This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Nokogiri
|
||||
module CSS
|
||||
class Node # :nodoc:
|
||||
ALLOW_COMBINATOR_ON_SELF = [:DIRECT_ADJACENT_SELECTOR, :FOLLOWING_SELECTOR, :CHILD_SELECTOR]
|
||||
|
||||
# Get the type of this node
|
||||
attr_accessor :type
|
||||
# Get the value of this node
|
||||
attr_accessor :value
|
||||
|
||||
# Create a new Node with +type+ and +value+
|
||||
def initialize(type, value)
|
||||
@type = type
|
||||
@value = value
|
||||
end
|
||||
|
||||
# Accept +visitor+
|
||||
def accept(visitor)
|
||||
visitor.send(:"visit_#{type.to_s.downcase}", self)
|
||||
end
|
||||
|
||||
###
|
||||
# Convert this CSS node to xpath with +prefix+ using +visitor+
|
||||
def to_xpath(visitor)
|
||||
prefix = if ALLOW_COMBINATOR_ON_SELF.include?(type) && value.first.nil?
|
||||
"."
|
||||
else
|
||||
visitor.prefix
|
||||
end
|
||||
prefix + visitor.accept(self)
|
||||
end
|
||||
|
||||
# Find a node by type using +types+
|
||||
def find_by_type(types)
|
||||
matches = []
|
||||
matches << self if to_type == types
|
||||
@value.each do |v|
|
||||
matches += v.find_by_type(types) if v.respond_to?(:find_by_type)
|
||||
end
|
||||
matches
|
||||
end
|
||||
|
||||
# Convert to_type
|
||||
def to_type
|
||||
[@type] + @value.filter_map do |n|
|
||||
n.to_type if n.respond_to?(:to_type)
|
||||
end
|
||||
end
|
||||
|
||||
# Convert to array
|
||||
def to_a
|
||||
[@type] + @value.map { |n| n.respond_to?(:to_a) ? n.to_a : [n] }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,772 @@
|
||||
# frozen_string_literal: true
|
||||
#
|
||||
# DO NOT MODIFY!!!!
|
||||
# This file is automatically generated by Racc 1.8.0
|
||||
# from Racc grammar file "parser.y".
|
||||
#
|
||||
|
||||
require 'racc/parser.rb'
|
||||
|
||||
|
||||
require_relative "parser_extras"
|
||||
|
||||
module Nokogiri
|
||||
module CSS
|
||||
# :nodoc: all
|
||||
class Parser < Racc::Parser
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Nokogiri
|
||||
module CSS
|
||||
class Parser < Racc::Parser
|
||||
|
||||
|
||||
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
|
||||
##### State transition tables begin ###
|
||||
|
||||
racc_action_table = [
|
||||
27, 11, 38, 99, 36, 12, 40, 26, 48, 25,
|
||||
49, 27, 100, 12, 30, 36, 105, 99, -26, 28,
|
||||
25, -26, 26, 27, 29, 14, 21, 23, 80, 30,
|
||||
28, 36, 72, 26, -26, 29, 14, 21, 23, 27,
|
||||
30, 91, 56, 36, 97, 96, 43, 29, 25, 26,
|
||||
27, 92, 94, 21, 36, 95, 30, 98, 28, 25,
|
||||
101, 26, 102, 29, 14, 21, 23, 96, 30, 28,
|
||||
36, 36, 26, 103, 29, 14, 21, 23, 27, 30,
|
||||
108, 107, 36, 109, 106, 43, 43, 25, 26, 26,
|
||||
27, 110, 21, 21, 111, 30, 30, 28, 99, 50,
|
||||
26, 53, 29, 14, 21, 23, 36, 30, 36, 56,
|
||||
61, 64, 113, 66, 29, 14, 116, 36, 118, 36,
|
||||
nil, 43, nil, 43, 26, nil, 26, 14, 21, 23,
|
||||
21, 30, 43, 30, 43, 26, nil, 26, 36, 21,
|
||||
36, 21, 30, 25, 30, nil, nil, nil, nil, nil,
|
||||
nil, 61, 62, 43, 60, 43, 26, nil, 26, nil,
|
||||
21, 23, 21, 30, 57, 30, 88, 89, 14, nil,
|
||||
nil, 88, 89, nil, nil, nil, nil, 84, 85, 86,
|
||||
nil, 87, 84, 85, 86, 83, 87, nil, 61, 93,
|
||||
83, 66, 61, 93, nil, 66, 61, 93, nil, 66,
|
||||
61, 93, nil, 66, nil, 14, nil, 61, 93, 14,
|
||||
66, nil, nil, 14, nil, nil, nil, 14, 4, 5,
|
||||
10, nil, nil, nil, 14, 4, 5, 47, 6, nil,
|
||||
8, 7, 4, 5, 10, 6, nil, 8, 7, nil,
|
||||
nil, nil, 6, nil, 8, 7 ]
|
||||
|
||||
racc_action_check = [
|
||||
3, 1, 11, 64, 3, 70, 14, 17, 21, 3,
|
||||
24, 9, 62, 1, 17, 9, 70, 62, 25, 3,
|
||||
9, 64, 3, 30, 3, 3, 3, 3, 49, 3,
|
||||
9, 16, 30, 9, 50, 9, 9, 9, 9, 12,
|
||||
9, 53, 30, 12, 60, 60, 16, 30, 12, 16,
|
||||
46, 54, 58, 16, 46, 59, 16, 61, 12, 46,
|
||||
63, 12, 65, 12, 12, 12, 12, 66, 12, 46,
|
||||
31, 32, 46, 67, 46, 46, 46, 46, 47, 46,
|
||||
82, 82, 47, 82, 81, 31, 32, 47, 31, 32,
|
||||
26, 90, 31, 32, 92, 31, 32, 47, 93, 26,
|
||||
47, 26, 47, 47, 47, 47, 28, 47, 33, 26,
|
||||
28, 28, 97, 28, 26, 26, 100, 34, 113, 35,
|
||||
nil, 28, nil, 33, 28, nil, 33, 28, 28, 28,
|
||||
33, 28, 34, 33, 35, 34, nil, 35, 43, 34,
|
||||
68, 35, 34, 43, 35, nil, nil, nil, nil, nil,
|
||||
nil, 27, 27, 43, 27, 68, 43, nil, 68, nil,
|
||||
43, 43, 68, 43, 27, 68, 51, 51, 27, nil,
|
||||
nil, 52, 52, nil, nil, nil, nil, 51, 51, 51,
|
||||
nil, 51, 52, 52, 52, 51, 52, nil, 56, 56,
|
||||
52, 56, 96, 96, nil, 96, 98, 98, nil, 98,
|
||||
99, 99, nil, 99, nil, 56, nil, 101, 101, 96,
|
||||
101, nil, nil, 98, nil, nil, nil, 99, 0, 0,
|
||||
0, nil, nil, nil, 101, 20, 20, 20, 0, nil,
|
||||
0, 0, 29, 29, 29, 20, nil, 20, 20, nil,
|
||||
nil, nil, 29, nil, 29, 29 ]
|
||||
|
||||
racc_action_pointer = [
|
||||
211, 1, nil, -2, nil, nil, nil, nil, nil, 9,
|
||||
nil, 2, 37, nil, -5, nil, 25, -17, nil, nil,
|
||||
218, -3, nil, nil, -20, -12, 88, 141, 100, 225,
|
||||
21, 64, 65, 102, 111, 113, nil, nil, nil, nil,
|
||||
nil, nil, nil, 132, nil, nil, 48, 76, nil, 17,
|
||||
4, 163, 168, 16, 21, nil, 178, nil, 29, 32,
|
||||
33, 45, 5, 48, -9, 39, 55, 50, 134, nil,
|
||||
-7, nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
||||
nil, 59, 70, nil, nil, nil, nil, nil, nil, nil,
|
||||
66, nil, 83, 86, nil, nil, 182, 105, 186, 190,
|
||||
103, 197, nil, nil, nil, nil, nil, nil, nil, nil,
|
||||
nil, nil, nil, 105, nil, nil, nil, nil, nil ]
|
||||
|
||||
racc_action_default = [
|
||||
-81, -82, -2, -27, -4, -5, -6, -7, -8, -27,
|
||||
-80, -82, -27, -3, -82, -10, -53, -12, -15, -16,
|
||||
-20, -82, -22, -23, -82, -25, -27, -82, -27, -81,
|
||||
-82, -59, -60, -61, -62, -63, -64, -17, 119, -1,
|
||||
-9, -11, -52, -27, -13, -14, -27, -27, -21, -82,
|
||||
-32, -68, -68, -82, -82, -33, -82, -34, -82, -82,
|
||||
-43, -44, -45, -46, -25, -82, -43, -82, -77, -79,
|
||||
-82, -50, -51, -54, -55, -56, -57, -58, -18, -19,
|
||||
-24, -82, -82, -69, -70, -71, -72, -73, -74, -75,
|
||||
-82, -30, -82, -45, -35, -36, -82, -49, -82, -82,
|
||||
-82, -82, -37, -76, -78, -38, -28, -65, -66, -67,
|
||||
-29, -31, -39, -82, -40, -41, -48, -42, -47 ]
|
||||
|
||||
racc_goto_table = [
|
||||
58, 42, 13, 1, 46, 52, 19, 68, 37, 71,
|
||||
41, 39, 19, 69, 44, 19, 73, 74, 75, 76,
|
||||
77, 45, 68, 81, 90, 54, 51, 59, 69, 55,
|
||||
nil, nil, 70, nil, nil, nil, nil, nil, nil, nil,
|
||||
nil, nil, nil, nil, nil, 78, 79, nil, nil, 19,
|
||||
19, nil, nil, 104, nil, nil, nil, nil, nil, nil,
|
||||
nil, nil, nil, nil, nil, nil, nil, nil, nil, 112,
|
||||
nil, 114, 115, nil, 117 ]
|
||||
|
||||
racc_goto_check = [
|
||||
20, 14, 2, 1, 5, 11, 7, 9, 2, 11,
|
||||
10, 2, 7, 14, 12, 7, 14, 14, 14, 14,
|
||||
14, 13, 9, 19, 19, 17, 18, 21, 14, 7,
|
||||
nil, nil, 1, nil, nil, nil, nil, nil, nil, nil,
|
||||
nil, nil, nil, nil, nil, 2, 2, nil, nil, 7,
|
||||
7, nil, nil, 14, nil, nil, nil, nil, nil, nil,
|
||||
nil, nil, nil, nil, nil, nil, nil, nil, nil, 20,
|
||||
nil, 20, 20, nil, 20 ]
|
||||
|
||||
racc_goto_pointer = [
|
||||
nil, 3, -1, nil, nil, -16, nil, 3, nil, -21,
|
||||
-6, -21, -3, 4, -15, nil, nil, -1, 0, -28,
|
||||
-27, 0, nil, nil, nil, nil ]
|
||||
|
||||
racc_goto_default = [
|
||||
nil, nil, nil, 2, 3, 9, 15, 63, 20, 16,
|
||||
nil, 17, 34, 33, 18, 32, 22, 24, nil, nil,
|
||||
65, nil, 31, 35, 82, 67 ]
|
||||
|
||||
racc_reduce_table = [
|
||||
0, 0, :racc_error,
|
||||
3, 33, :_reduce_1,
|
||||
1, 33, :_reduce_2,
|
||||
2, 33, :_reduce_3,
|
||||
1, 37, :_reduce_4,
|
||||
1, 37, :_reduce_5,
|
||||
1, 37, :_reduce_6,
|
||||
1, 37, :_reduce_7,
|
||||
1, 37, :_reduce_8,
|
||||
2, 38, :_reduce_9,
|
||||
1, 39, :_reduce_10,
|
||||
2, 40, :_reduce_11,
|
||||
1, 40, :_reduce_none,
|
||||
2, 40, :_reduce_13,
|
||||
2, 40, :_reduce_14,
|
||||
1, 40, :_reduce_15,
|
||||
1, 40, :_reduce_none,
|
||||
2, 35, :_reduce_17,
|
||||
3, 34, :_reduce_18,
|
||||
3, 34, :_reduce_19,
|
||||
1, 34, :_reduce_none,
|
||||
2, 47, :_reduce_21,
|
||||
1, 41, :_reduce_none,
|
||||
1, 41, :_reduce_23,
|
||||
3, 48, :_reduce_24,
|
||||
1, 48, :_reduce_25,
|
||||
1, 49, :_reduce_26,
|
||||
0, 49, :_reduce_none,
|
||||
4, 45, :_reduce_28,
|
||||
4, 45, :_reduce_29,
|
||||
3, 45, :_reduce_30,
|
||||
3, 50, :_reduce_31,
|
||||
1, 50, :_reduce_32,
|
||||
1, 50, :_reduce_none,
|
||||
2, 43, :_reduce_34,
|
||||
3, 43, :_reduce_35,
|
||||
3, 43, :_reduce_36,
|
||||
3, 43, :_reduce_37,
|
||||
3, 43, :_reduce_38,
|
||||
3, 52, :_reduce_39,
|
||||
3, 52, :_reduce_40,
|
||||
3, 52, :_reduce_41,
|
||||
3, 52, :_reduce_42,
|
||||
1, 52, :_reduce_none,
|
||||
1, 52, :_reduce_none,
|
||||
1, 52, :_reduce_45,
|
||||
1, 52, :_reduce_none,
|
||||
4, 53, :_reduce_47,
|
||||
3, 53, :_reduce_48,
|
||||
2, 53, :_reduce_49,
|
||||
2, 44, :_reduce_50,
|
||||
2, 44, :_reduce_51,
|
||||
1, 42, :_reduce_none,
|
||||
0, 42, :_reduce_none,
|
||||
2, 46, :_reduce_54,
|
||||
2, 46, :_reduce_55,
|
||||
2, 46, :_reduce_56,
|
||||
2, 46, :_reduce_57,
|
||||
2, 46, :_reduce_58,
|
||||
1, 46, :_reduce_none,
|
||||
1, 46, :_reduce_none,
|
||||
1, 46, :_reduce_none,
|
||||
1, 46, :_reduce_none,
|
||||
1, 46, :_reduce_none,
|
||||
1, 54, :_reduce_64,
|
||||
2, 51, :_reduce_65,
|
||||
2, 51, :_reduce_66,
|
||||
2, 51, :_reduce_67,
|
||||
0, 51, :_reduce_none,
|
||||
1, 56, :_reduce_69,
|
||||
1, 56, :_reduce_70,
|
||||
1, 56, :_reduce_71,
|
||||
1, 56, :_reduce_72,
|
||||
1, 56, :_reduce_73,
|
||||
1, 56, :_reduce_74,
|
||||
1, 56, :_reduce_75,
|
||||
3, 55, :_reduce_76,
|
||||
1, 57, :_reduce_none,
|
||||
2, 57, :_reduce_none,
|
||||
1, 57, :_reduce_none,
|
||||
1, 36, :_reduce_none,
|
||||
0, 36, :_reduce_none ]
|
||||
|
||||
racc_reduce_n = 82
|
||||
|
||||
racc_shift_n = 119
|
||||
|
||||
racc_token_table = {
|
||||
false => 0,
|
||||
:error => 1,
|
||||
:FUNCTION => 2,
|
||||
:INCLUDES => 3,
|
||||
:DASHMATCH => 4,
|
||||
:LBRACE => 5,
|
||||
:HASH => 6,
|
||||
:PLUS => 7,
|
||||
:GREATER => 8,
|
||||
:S => 9,
|
||||
:STRING => 10,
|
||||
:IDENT => 11,
|
||||
:COMMA => 12,
|
||||
:NUMBER => 13,
|
||||
:PREFIXMATCH => 14,
|
||||
:SUFFIXMATCH => 15,
|
||||
:SUBSTRINGMATCH => 16,
|
||||
:TILDE => 17,
|
||||
:NOT_EQUAL => 18,
|
||||
:SLASH => 19,
|
||||
:DOUBLESLASH => 20,
|
||||
:NOT => 21,
|
||||
:EQUAL => 22,
|
||||
:RPAREN => 23,
|
||||
:LSQUARE => 24,
|
||||
:RSQUARE => 25,
|
||||
:HAS => 26,
|
||||
"@" => 27,
|
||||
"." => 28,
|
||||
"*" => 29,
|
||||
"|" => 30,
|
||||
":" => 31 }
|
||||
|
||||
racc_nt_base = 32
|
||||
|
||||
racc_use_result_var = true
|
||||
|
||||
Racc_arg = [
|
||||
racc_action_table,
|
||||
racc_action_check,
|
||||
racc_action_default,
|
||||
racc_action_pointer,
|
||||
racc_goto_table,
|
||||
racc_goto_check,
|
||||
racc_goto_default,
|
||||
racc_goto_pointer,
|
||||
racc_nt_base,
|
||||
racc_reduce_table,
|
||||
racc_token_table,
|
||||
racc_shift_n,
|
||||
racc_reduce_n,
|
||||
racc_use_result_var ]
|
||||
Ractor.make_shareable(Racc_arg) if defined?(Ractor)
|
||||
|
||||
Racc_token_to_s_table = [
|
||||
"$end",
|
||||
"error",
|
||||
"FUNCTION",
|
||||
"INCLUDES",
|
||||
"DASHMATCH",
|
||||
"LBRACE",
|
||||
"HASH",
|
||||
"PLUS",
|
||||
"GREATER",
|
||||
"S",
|
||||
"STRING",
|
||||
"IDENT",
|
||||
"COMMA",
|
||||
"NUMBER",
|
||||
"PREFIXMATCH",
|
||||
"SUFFIXMATCH",
|
||||
"SUBSTRINGMATCH",
|
||||
"TILDE",
|
||||
"NOT_EQUAL",
|
||||
"SLASH",
|
||||
"DOUBLESLASH",
|
||||
"NOT",
|
||||
"EQUAL",
|
||||
"RPAREN",
|
||||
"LSQUARE",
|
||||
"RSQUARE",
|
||||
"HAS",
|
||||
"\"@\"",
|
||||
"\".\"",
|
||||
"\"*\"",
|
||||
"\"|\"",
|
||||
"\":\"",
|
||||
"$start",
|
||||
"selector",
|
||||
"simple_selector_1toN",
|
||||
"prefixless_combinator_selector",
|
||||
"optional_S",
|
||||
"combinator",
|
||||
"xpath_attribute_name",
|
||||
"xpath_attribute",
|
||||
"simple_selector",
|
||||
"element_name",
|
||||
"hcap_0toN",
|
||||
"function",
|
||||
"pseudo",
|
||||
"attrib",
|
||||
"hcap_1toN",
|
||||
"class",
|
||||
"namespaced_ident",
|
||||
"namespace",
|
||||
"attrib_name",
|
||||
"attrib_val_0or1",
|
||||
"expr",
|
||||
"nth",
|
||||
"attribute_id",
|
||||
"negation",
|
||||
"eql_incl_dash",
|
||||
"negation_arg" ]
|
||||
Ractor.make_shareable(Racc_token_to_s_table) if defined?(Ractor)
|
||||
|
||||
Racc_debug_parser = false
|
||||
|
||||
##### State transition tables end #####
|
||||
|
||||
# reduce 0 omitted
|
||||
|
||||
def _reduce_1(val, _values, result)
|
||||
result = [val[0], val[2]].flatten
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_2(val, _values, result)
|
||||
result = val.flatten
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_3(val, _values, result)
|
||||
result = [val[1]].flatten
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_4(val, _values, result)
|
||||
result = :DIRECT_ADJACENT_SELECTOR
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_5(val, _values, result)
|
||||
result = :CHILD_SELECTOR
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_6(val, _values, result)
|
||||
result = :FOLLOWING_SELECTOR
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_7(val, _values, result)
|
||||
result = :DESCENDANT_SELECTOR
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_8(val, _values, result)
|
||||
result = :CHILD_SELECTOR
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_9(val, _values, result)
|
||||
result = val[1]
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_10(val, _values, result)
|
||||
result = Node.new(:ATTRIB_NAME, [val[0]])
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_11(val, _values, result)
|
||||
result = if val[1].nil?
|
||||
val[0]
|
||||
else
|
||||
Node.new(:CONDITIONAL_SELECTOR, [val[0], val[1]])
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
# reduce 12 omitted
|
||||
|
||||
def _reduce_13(val, _values, result)
|
||||
result = Node.new(:CONDITIONAL_SELECTOR, val)
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_14(val, _values, result)
|
||||
result = Node.new(:CONDITIONAL_SELECTOR, val)
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_15(val, _values, result)
|
||||
result = Node.new(:CONDITIONAL_SELECTOR, [Node.new(:ELEMENT_NAME, ['*']), val[0]])
|
||||
result
|
||||
end
|
||||
|
||||
# reduce 16 omitted
|
||||
|
||||
def _reduce_17(val, _values, result)
|
||||
result = Node.new(val[0], [nil, val[1]])
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_18(val, _values, result)
|
||||
result = Node.new(val[1], [val[0], val[2]])
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_19(val, _values, result)
|
||||
result = Node.new(:DESCENDANT_SELECTOR, [val[0], val[2]])
|
||||
result
|
||||
end
|
||||
|
||||
# reduce 20 omitted
|
||||
|
||||
def _reduce_21(val, _values, result)
|
||||
result = Node.new(:CLASS_CONDITION, [unescape_css_identifier(val[1])])
|
||||
result
|
||||
end
|
||||
|
||||
# reduce 22 omitted
|
||||
|
||||
def _reduce_23(val, _values, result)
|
||||
result = Node.new(:ELEMENT_NAME, val)
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_24(val, _values, result)
|
||||
result = Node.new(:ELEMENT_NAME, [val[0], val[2]])
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_25(val, _values, result)
|
||||
name = val[0]
|
||||
result = Node.new(:ELEMENT_NAME, [name])
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_26(val, _values, result)
|
||||
result = val[0]
|
||||
result
|
||||
end
|
||||
|
||||
# reduce 27 omitted
|
||||
|
||||
def _reduce_28(val, _values, result)
|
||||
result = Node.new(:ATTRIBUTE_CONDITION, [val[1]] + (val[2] || []))
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_29(val, _values, result)
|
||||
result = Node.new(:ATTRIBUTE_CONDITION, [val[1]] + (val[2] || []))
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_30(val, _values, result)
|
||||
result = Node.new(:PSEUDO_CLASS, [Node.new(:FUNCTION, ['nth-child(', val[1]])])
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_31(val, _values, result)
|
||||
result = Node.new(:ATTRIB_NAME, [[val[0], val[2]].compact.join(':')])
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_32(val, _values, result)
|
||||
result = Node.new(:ATTRIB_NAME, [val[0]])
|
||||
result
|
||||
end
|
||||
|
||||
# reduce 33 omitted
|
||||
|
||||
def _reduce_34(val, _values, result)
|
||||
result = Node.new(:FUNCTION, [val[0].strip])
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_35(val, _values, result)
|
||||
result = Node.new(:FUNCTION, [val[0].strip, val[1]].flatten)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_36(val, _values, result)
|
||||
result = Node.new(:FUNCTION, [val[0].strip, val[1]].flatten)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_37(val, _values, result)
|
||||
result = Node.new(:FUNCTION, [val[0].strip, val[1]].flatten)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_38(val, _values, result)
|
||||
result = Node.new(:FUNCTION, [val[0].strip, val[1]].flatten)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_39(val, _values, result)
|
||||
result = [val[0], val[2]]
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_40(val, _values, result)
|
||||
result = [val[0], val[2]]
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_41(val, _values, result)
|
||||
result = [val[0], val[2]]
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_42(val, _values, result)
|
||||
result = [val[0], val[2]]
|
||||
result
|
||||
end
|
||||
|
||||
# reduce 43 omitted
|
||||
|
||||
# reduce 44 omitted
|
||||
|
||||
def _reduce_45(val, _values, result)
|
||||
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
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
# reduce 46 omitted
|
||||
|
||||
def _reduce_47(val, _values, result)
|
||||
if val[1] == 'n'
|
||||
result = Node.new(:NTH, val)
|
||||
else
|
||||
raise Racc::ParseError, "parse error on IDENT '#{val[1]}'"
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_48(val, _values, result)
|
||||
# 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
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_49(val, _values, result)
|
||||
# 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
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_50(val, _values, result)
|
||||
result = Node.new(:PSEUDO_CLASS, [val[1]])
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_51(val, _values, result)
|
||||
result = Node.new(:PSEUDO_CLASS, [val[1]])
|
||||
result
|
||||
end
|
||||
|
||||
# reduce 52 omitted
|
||||
|
||||
# reduce 53 omitted
|
||||
|
||||
def _reduce_54(val, _values, result)
|
||||
result = Node.new(:COMBINATOR, val)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_55(val, _values, result)
|
||||
result = Node.new(:COMBINATOR, val)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_56(val, _values, result)
|
||||
result = Node.new(:COMBINATOR, val)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_57(val, _values, result)
|
||||
result = Node.new(:COMBINATOR, val)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_58(val, _values, result)
|
||||
result = Node.new(:COMBINATOR, val)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
# reduce 59 omitted
|
||||
|
||||
# reduce 60 omitted
|
||||
|
||||
# reduce 61 omitted
|
||||
|
||||
# reduce 62 omitted
|
||||
|
||||
# reduce 63 omitted
|
||||
|
||||
def _reduce_64(val, _values, result)
|
||||
result = Node.new(:ID, [unescape_css_identifier(val[0])])
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_65(val, _values, result)
|
||||
result = [val[0], unescape_css_identifier(val[1])]
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_66(val, _values, result)
|
||||
result = [val[0], unescape_css_string(val[1])]
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_67(val, _values, result)
|
||||
result = [val[0], val[1]]
|
||||
result
|
||||
end
|
||||
|
||||
# reduce 68 omitted
|
||||
|
||||
def _reduce_69(val, _values, result)
|
||||
result = :equal
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_70(val, _values, result)
|
||||
result = :prefix_match
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_71(val, _values, result)
|
||||
result = :suffix_match
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_72(val, _values, result)
|
||||
result = :substring_match
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_73(val, _values, result)
|
||||
result = :not_equal
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_74(val, _values, result)
|
||||
result = :includes
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_75(val, _values, result)
|
||||
result = :dash_match
|
||||
result
|
||||
end
|
||||
|
||||
def _reduce_76(val, _values, result)
|
||||
result = Node.new(:NOT, [val[1]])
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
# reduce 77 omitted
|
||||
|
||||
# reduce 78 omitted
|
||||
|
||||
# reduce 79 omitted
|
||||
|
||||
# reduce 80 omitted
|
||||
|
||||
# reduce 81 omitted
|
||||
|
||||
def _reduce_none(val, _values, result)
|
||||
val[0]
|
||||
end
|
||||
|
||||
end # class Parser
|
||||
end # module CSS
|
||||
end # module Nokogiri
|
||||
@@ -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
|
||||
@@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "thread"
|
||||
|
||||
module Nokogiri
|
||||
module CSS
|
||||
class Parser < Racc::Parser # :nodoc:
|
||||
def initialize
|
||||
@tokenizer = Tokenizer.new
|
||||
super
|
||||
end
|
||||
|
||||
def parse(string)
|
||||
@tokenizer.scan_setup(string)
|
||||
do_parse
|
||||
end
|
||||
|
||||
def next_token
|
||||
@tokenizer.next_token
|
||||
end
|
||||
|
||||
# Get the xpath for +selector+ using +visitor+
|
||||
def xpath_for(selector, visitor)
|
||||
parse(selector).map do |ast|
|
||||
ast.to_xpath(visitor)
|
||||
end
|
||||
end
|
||||
|
||||
# On CSS parser error, raise an exception
|
||||
def on_error(error_token_id, error_value, value_stack)
|
||||
after = value_stack.compact.last
|
||||
raise SyntaxError, "unexpected '#{error_value}' after '#{after}'"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Nokogiri
|
||||
module CSS
|
||||
module SelectorCache # :nodoc:
|
||||
@cache = {}
|
||||
@mutex = Mutex.new
|
||||
|
||||
class << self
|
||||
# Retrieve the cached XPath expressions for the key
|
||||
def [](key)
|
||||
@mutex.synchronize { @cache[key] }
|
||||
end
|
||||
|
||||
# Insert the XPath expressions `value` at the cache key
|
||||
def []=(key, value)
|
||||
@mutex.synchronize { @cache[key] = value }
|
||||
end
|
||||
|
||||
# Clear the cache
|
||||
def clear_cache(create_new_object = false)
|
||||
@mutex.synchronize do
|
||||
if create_new_object # used in tests to avoid 'method redefined' warnings when injecting spies
|
||||
@cache = {}
|
||||
else
|
||||
@cache.clear
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Construct a unique key cache key
|
||||
def key(selector:, visitor:)
|
||||
[selector, visitor.config]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "../syntax_error"
|
||||
module Nokogiri
|
||||
module CSS
|
||||
class SyntaxError < ::Nokogiri::SyntaxError
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,155 @@
|
||||
# frozen_string_literal: true
|
||||
#--
|
||||
# DO NOT MODIFY!!!!
|
||||
# This file is automatically generated by rex 1.0.7
|
||||
# from lexical definition file "lib/nokogiri/css/tokenizer.rex".
|
||||
#++
|
||||
|
||||
module Nokogiri
|
||||
module CSS
|
||||
# :nodoc: all
|
||||
class Tokenizer
|
||||
require 'strscan'
|
||||
|
||||
class ScanError < StandardError ; end
|
||||
|
||||
attr_reader :lineno
|
||||
attr_reader :filename
|
||||
attr_accessor :state
|
||||
|
||||
def scan_setup(str)
|
||||
@ss = StringScanner.new(str)
|
||||
@lineno = 1
|
||||
@state = nil
|
||||
end
|
||||
|
||||
def action
|
||||
yield
|
||||
end
|
||||
|
||||
def scan_str(str)
|
||||
scan_setup(str)
|
||||
do_parse
|
||||
end
|
||||
alias :scan :scan_str
|
||||
|
||||
def load_file( filename )
|
||||
@filename = filename
|
||||
File.open(filename, "r") do |f|
|
||||
scan_setup(f.read)
|
||||
end
|
||||
end
|
||||
|
||||
def scan_file( filename )
|
||||
load_file(filename)
|
||||
do_parse
|
||||
end
|
||||
|
||||
|
||||
def next_token
|
||||
return if @ss.eos?
|
||||
|
||||
# skips empty actions
|
||||
until token = _next_token or @ss.eos?; end
|
||||
token
|
||||
end
|
||||
|
||||
def _next_token
|
||||
text = @ss.peek(1)
|
||||
@lineno += 1 if text == "\n"
|
||||
token = case @state
|
||||
when nil
|
||||
case
|
||||
when (text = @ss.scan(/has\([\s]*/))
|
||||
action { [:HAS, text] }
|
||||
|
||||
when (text = @ss.scan(/-?([_A-Za-z]|[^\0-\177]|(\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f]))([_A-Za-z0-9-]|[^\0-\177]|(\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f]))*\([\s]*/))
|
||||
action { [:FUNCTION, text] }
|
||||
|
||||
when (text = @ss.scan(/-?([_A-Za-z]|[^\0-\177]|(\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f]))([_A-Za-z0-9-]|[^\0-\177]|(\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f]))*/))
|
||||
action { [:IDENT, text] }
|
||||
|
||||
when (text = @ss.scan(/\#([_A-Za-z0-9-]|[^\0-\177]|(\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f]))+/))
|
||||
action { [:HASH, text] }
|
||||
|
||||
when (text = @ss.scan(/[\s]*~=[\s]*/))
|
||||
action { [:INCLUDES, text] }
|
||||
|
||||
when (text = @ss.scan(/[\s]*\|=[\s]*/))
|
||||
action { [:DASHMATCH, text] }
|
||||
|
||||
when (text = @ss.scan(/[\s]*\^=[\s]*/))
|
||||
action { [:PREFIXMATCH, text] }
|
||||
|
||||
when (text = @ss.scan(/[\s]*\$=[\s]*/))
|
||||
action { [:SUFFIXMATCH, text] }
|
||||
|
||||
when (text = @ss.scan(/[\s]*\*=[\s]*/))
|
||||
action { [:SUBSTRINGMATCH, text] }
|
||||
|
||||
when (text = @ss.scan(/[\s]*!=[\s]*/))
|
||||
action { [:NOT_EQUAL, text] }
|
||||
|
||||
when (text = @ss.scan(/[\s]*=[\s]*/))
|
||||
action { [:EQUAL, text] }
|
||||
|
||||
when (text = @ss.scan(/[\s]*\)/))
|
||||
action { [:RPAREN, text] }
|
||||
|
||||
when (text = @ss.scan(/\[[\s]*/))
|
||||
action { [:LSQUARE, text] }
|
||||
|
||||
when (text = @ss.scan(/[\s]*\]/))
|
||||
action { [:RSQUARE, text] }
|
||||
|
||||
when (text = @ss.scan(/[\s]*\+[\s]*/))
|
||||
action { [:PLUS, text] }
|
||||
|
||||
when (text = @ss.scan(/[\s]*>[\s]*/))
|
||||
action { [:GREATER, text] }
|
||||
|
||||
when (text = @ss.scan(/[\s]*,[\s]*/))
|
||||
action { [:COMMA, text] }
|
||||
|
||||
when (text = @ss.scan(/[\s]*~[\s]*/))
|
||||
action { [:TILDE, text] }
|
||||
|
||||
when (text = @ss.scan(/\:not\([\s]*/))
|
||||
action { [:NOT, text] }
|
||||
|
||||
when (text = @ss.scan(/-?([0-9]+|[0-9]*\.[0-9]+)/))
|
||||
action { [:NUMBER, text] }
|
||||
|
||||
when (text = @ss.scan(/[\s]*\/\/[\s]*/))
|
||||
action { [:DOUBLESLASH, text] }
|
||||
|
||||
when (text = @ss.scan(/[\s]*\/[\s]*/))
|
||||
action { [:SLASH, text] }
|
||||
|
||||
when (text = @ss.scan(/U\+[0-9a-f?]{1,6}(-[0-9a-f]{1,6})?/))
|
||||
action {[:UNICODE_RANGE, text] }
|
||||
|
||||
when (text = @ss.scan(/[\s]+/))
|
||||
action { [:S, text] }
|
||||
|
||||
when (text = @ss.scan(/("([^\n\r\f"]|(\n|\r\n|\r|\f)|[^\0-\177]|(\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f]))*(?<!\\)(?:\\{2})*"|'([^\n\r\f']|(\n|\r\n|\r|\f)|[^\0-\177]|(\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f]))*(?<!\\)(?:\\{2})*')/))
|
||||
action { [:STRING, text] }
|
||||
|
||||
when (text = @ss.scan(/./))
|
||||
action { [text, text] }
|
||||
|
||||
|
||||
else
|
||||
text = @ss.string[@ss.pos .. -1]
|
||||
raise ScanError, "can not match: '" + text + "'"
|
||||
end # if
|
||||
|
||||
else
|
||||
raise ScanError, "undefined state: '" + state.to_s + "'"
|
||||
end # case state
|
||||
token
|
||||
end # def _next_token
|
||||
|
||||
end # class
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,57 @@
|
||||
module Nokogiri
|
||||
module CSS
|
||||
# :nodoc: all
|
||||
class Tokenizer
|
||||
|
||||
macro
|
||||
nl (\n|\r\n|\r|\f)
|
||||
w [\s]*
|
||||
nonascii [^\0-\177]
|
||||
num -?([0-9]+|[0-9]*\.[0-9]+)
|
||||
unicode \\[0-9A-Fa-f]{1,6}(\r\n|[\s])?
|
||||
|
||||
escape ({unicode}|\\[^\n\r\f0-9A-Fa-f])
|
||||
nmchar ([_A-Za-z0-9-]|{nonascii}|{escape})
|
||||
nmstart ([_A-Za-z]|{nonascii}|{escape})
|
||||
name {nmstart}{nmchar}*
|
||||
ident -?{name}
|
||||
charref {nmchar}+
|
||||
string1 "([^\n\r\f"]|{nl}|{nonascii}|{escape})*(?<!\\)(?:\\{2})*"
|
||||
string2 '([^\n\r\f']|{nl}|{nonascii}|{escape})*(?<!\\)(?:\\{2})*'
|
||||
string ({string1}|{string2})
|
||||
|
||||
rule
|
||||
|
||||
# [:state] pattern [actions]
|
||||
|
||||
has\({w} { [:HAS, text] }
|
||||
{ident}\({w} { [:FUNCTION, text] }
|
||||
{ident} { [:IDENT, text] }
|
||||
\#{charref} { [:HASH, text] }
|
||||
{w}~={w} { [:INCLUDES, text] }
|
||||
{w}\|={w} { [:DASHMATCH, text] }
|
||||
{w}\^={w} { [:PREFIXMATCH, text] }
|
||||
{w}\$={w} { [:SUFFIXMATCH, text] }
|
||||
{w}\*={w} { [:SUBSTRINGMATCH, text] }
|
||||
{w}!={w} { [:NOT_EQUAL, text] }
|
||||
{w}={w} { [:EQUAL, text] }
|
||||
{w}\) { [:RPAREN, text] }
|
||||
\[{w} { [:LSQUARE, text] }
|
||||
{w}\] { [:RSQUARE, text] }
|
||||
{w}\+{w} { [:PLUS, text] }
|
||||
{w}>{w} { [:GREATER, text] }
|
||||
{w},{w} { [:COMMA, text] }
|
||||
{w}~{w} { [:TILDE, text] }
|
||||
\:not\({w} { [:NOT, text] }
|
||||
{num} { [:NUMBER, text] }
|
||||
{w}\/\/{w} { [:DOUBLESLASH, text] }
|
||||
{w}\/{w} { [:SLASH, text] }
|
||||
|
||||
U\+[0-9a-f?]{1,6}(-[0-9a-f]{1,6})? {[:UNICODE_RANGE, text] }
|
||||
|
||||
[\s]+ { [:S, text] }
|
||||
{string} { [:STRING, text] }
|
||||
. { [text, text] }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,376 @@
|
||||
# coding: utf-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Nokogiri
|
||||
module CSS
|
||||
# When translating CSS selectors to XPath queries with Nokogiri::CSS.xpath_for, the XPathVisitor
|
||||
# class allows for changing some of the behaviors related to builtin xpath functions and quirks
|
||||
# of HTML5.
|
||||
class XPathVisitor
|
||||
WILDCARD_NAMESPACES = Nokogiri.libxml2_patches.include?("0009-allow-wildcard-namespaces.patch") # :nodoc:
|
||||
|
||||
# Enum to direct XPathVisitor when to use Nokogiri builtin XPath functions.
|
||||
module BuiltinsConfig
|
||||
# Never use Nokogiri builtin functions, always generate vanilla XPath 1.0 queries. This is
|
||||
# the default when calling Nokogiri::CSS.xpath_for directly.
|
||||
NEVER = :never
|
||||
|
||||
# Always use Nokogiri builtin functions whenever possible. This is probably only useful for testing.
|
||||
ALWAYS = :always
|
||||
|
||||
# Only use Nokogiri builtin functions when they will be faster than vanilla XPath. This is
|
||||
# the behavior chosen when searching for CSS selectors on a Nokogiri document, fragment, or
|
||||
# node.
|
||||
OPTIMAL = :optimal
|
||||
|
||||
# :nodoc: array of values for validation
|
||||
VALUES = [NEVER, ALWAYS, OPTIMAL]
|
||||
end
|
||||
|
||||
# Enum to direct XPathVisitor when to tweak the XPath query to suit the nature of the document
|
||||
# being searched. Note that searches for CSS selectors from a Nokogiri document, fragment, or
|
||||
# node will choose the correct option automatically.
|
||||
module DoctypeConfig
|
||||
# The document being searched is an XML document. This is the default.
|
||||
XML = :xml
|
||||
|
||||
# The document being searched is an HTML4 document.
|
||||
HTML4 = :html4
|
||||
|
||||
# The document being searched is an HTML5 document.
|
||||
HTML5 = :html5
|
||||
|
||||
# :nodoc: array of values for validation
|
||||
VALUES = [XML, HTML4, HTML5]
|
||||
end
|
||||
|
||||
# The visitor configuration set via the +builtins:+ keyword argument to XPathVisitor.new.
|
||||
attr_reader :builtins
|
||||
|
||||
# The visitor configuration set via the +doctype:+ keyword argument to XPathVisitor.new.
|
||||
attr_reader :doctype
|
||||
|
||||
# The visitor configuration set via the +prefix:+ keyword argument to XPathVisitor.new.
|
||||
attr_reader :prefix
|
||||
|
||||
# The visitor configuration set via the +namespaces:+ keyword argument to XPathVisitor.new.
|
||||
attr_reader :namespaces
|
||||
|
||||
# :call-seq:
|
||||
# new() → XPathVisitor
|
||||
# new(builtins:, doctype:) → XPathVisitor
|
||||
#
|
||||
# [Parameters]
|
||||
# - +builtins:+ (BuiltinsConfig) Determine when to use Nokogiri's built-in xpath functions for performance improvements.
|
||||
# - +doctype:+ (DoctypeConfig) Make document-type-specific accommodations for CSS queries.
|
||||
#
|
||||
# [Returns] XPathVisitor
|
||||
#
|
||||
def initialize(
|
||||
builtins: BuiltinsConfig::NEVER,
|
||||
doctype: DoctypeConfig::XML,
|
||||
prefix: Nokogiri::XML::XPath::GLOBAL_SEARCH_PREFIX,
|
||||
namespaces: nil
|
||||
)
|
||||
unless BuiltinsConfig::VALUES.include?(builtins)
|
||||
raise(ArgumentError, "Invalid values #{builtins.inspect} for builtins: keyword parameter")
|
||||
end
|
||||
unless DoctypeConfig::VALUES.include?(doctype)
|
||||
raise(ArgumentError, "Invalid values #{doctype.inspect} for doctype: keyword parameter")
|
||||
end
|
||||
|
||||
@builtins = builtins
|
||||
@doctype = doctype
|
||||
@prefix = prefix
|
||||
@namespaces = namespaces
|
||||
end
|
||||
|
||||
# :call-seq: config() → Hash
|
||||
#
|
||||
# [Returns]
|
||||
# a Hash representing the configuration of the XPathVisitor, suitable for use as
|
||||
# part of the CSS cache key.
|
||||
def config
|
||||
{ builtins: @builtins, doctype: @doctype, prefix: @prefix, namespaces: @namespaces }
|
||||
end
|
||||
|
||||
# :stopdoc:
|
||||
def visit_function(node)
|
||||
msg = :"visit_function_#{node.value.first.gsub(/[(]/, "")}"
|
||||
return send(msg, node) if respond_to?(msg)
|
||||
|
||||
case node.value.first
|
||||
when /^text\(/
|
||||
"child::text()"
|
||||
when /^self\(/
|
||||
"self::#{node.value[1]}"
|
||||
when /^eq\(/
|
||||
"position()=#{node.value[1]}"
|
||||
when /^(nth|nth-of-type)\(/
|
||||
if node.value[1].is_a?(Nokogiri::CSS::Node) && (node.value[1].type == :NTH)
|
||||
nth(node.value[1])
|
||||
else
|
||||
"position()=#{node.value[1]}"
|
||||
end
|
||||
when /^nth-child\(/
|
||||
if node.value[1].is_a?(Nokogiri::CSS::Node) && (node.value[1].type == :NTH)
|
||||
nth(node.value[1], child: true)
|
||||
else
|
||||
"count(preceding-sibling::*)=#{node.value[1].to_i - 1}"
|
||||
end
|
||||
when /^nth-last-of-type\(/
|
||||
if node.value[1].is_a?(Nokogiri::CSS::Node) && (node.value[1].type == :NTH)
|
||||
nth(node.value[1], last: true)
|
||||
else
|
||||
index = node.value[1].to_i - 1
|
||||
index == 0 ? "position()=last()" : "position()=last()-#{index}"
|
||||
end
|
||||
when /^nth-last-child\(/
|
||||
if node.value[1].is_a?(Nokogiri::CSS::Node) && (node.value[1].type == :NTH)
|
||||
nth(node.value[1], last: true, child: true)
|
||||
else
|
||||
"count(following-sibling::*)=#{node.value[1].to_i - 1}"
|
||||
end
|
||||
when /^(first|first-of-type)\(/
|
||||
"position()=1"
|
||||
when /^(last|last-of-type)\(/
|
||||
"position()=last()"
|
||||
when /^contains\(/
|
||||
"contains(.,#{node.value[1]})"
|
||||
when /^gt\(/
|
||||
"position()>#{node.value[1]}"
|
||||
when /^only-child\(/
|
||||
"last()=1"
|
||||
when /^comment\(/
|
||||
"comment()"
|
||||
when /^has\(/
|
||||
is_direct = node.value[1].value[0].nil? # e.g. "has(> a)", "has(~ a)", "has(+ a)"
|
||||
".#{"//" unless is_direct}#{node.value[1].accept(self)}"
|
||||
else
|
||||
validate_xpath_function_name(node.value.first)
|
||||
|
||||
# xpath function call, let's marshal those arguments
|
||||
args = ["."]
|
||||
args += node.value[1..-1].map do |n|
|
||||
n.is_a?(Nokogiri::CSS::Node) ? n.accept(self) : n
|
||||
end
|
||||
"nokogiri:#{node.value.first}#{args.join(",")})"
|
||||
end
|
||||
end
|
||||
|
||||
def visit_not(node)
|
||||
child = node.value.first
|
||||
if :ELEMENT_NAME == child.type
|
||||
"not(self::#{child.accept(self)})"
|
||||
else
|
||||
"not(#{child.accept(self)})"
|
||||
end
|
||||
end
|
||||
|
||||
def visit_id(node)
|
||||
node.value.first =~ /^#(.*)$/
|
||||
"@id='#{Regexp.last_match(1)}'"
|
||||
end
|
||||
|
||||
def visit_attribute_condition(node)
|
||||
attribute = node.value.first.accept(self)
|
||||
return attribute if node.value.length == 1
|
||||
|
||||
value = node.value.last
|
||||
value = "'#{value}'" unless /^['"]/.match?(value)
|
||||
|
||||
# quoted values - see test_attribute_value_with_quotes in test/css/test_parser.rb
|
||||
if (value[0] == value[-1]) && %q{"'}.include?(value[0])
|
||||
str_value = value[1..-2]
|
||||
if str_value.include?(value[0])
|
||||
value = 'concat("' + str_value.split('"', -1).join(%q{",'"',"}) + '","")'
|
||||
end
|
||||
end
|
||||
|
||||
case node.value[1]
|
||||
when :equal
|
||||
attribute + "=" + value.to_s
|
||||
when :not_equal
|
||||
attribute + "!=" + value.to_s
|
||||
when :substring_match
|
||||
"contains(#{attribute},#{value})"
|
||||
when :prefix_match
|
||||
"starts-with(#{attribute},#{value})"
|
||||
when :dash_match
|
||||
"#{attribute}=#{value} or starts-with(#{attribute},concat(#{value},'-'))"
|
||||
when :includes
|
||||
value = value[1..-2] # strip quotes
|
||||
css_class(attribute, value)
|
||||
when :suffix_match
|
||||
"substring(#{attribute},string-length(#{attribute})-string-length(#{value})+1,string-length(#{value}))=#{value}"
|
||||
else
|
||||
attribute + " #{node.value[1]} " + value.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def visit_pseudo_class(node)
|
||||
if node.value.first.is_a?(Nokogiri::CSS::Node) && (node.value.first.type == :FUNCTION)
|
||||
node.value.first.accept(self)
|
||||
else
|
||||
msg = :"visit_pseudo_class_#{node.value.first.gsub(/[(]/, "")}"
|
||||
return send(msg, node) if respond_to?(msg)
|
||||
|
||||
case node.value.first
|
||||
when "first" then "position()=1"
|
||||
when "first-child" then "count(preceding-sibling::*)=0"
|
||||
when "last" then "position()=last()"
|
||||
when "last-child" then "count(following-sibling::*)=0"
|
||||
when "first-of-type" then "position()=1"
|
||||
when "last-of-type" then "position()=last()"
|
||||
when "only-child" then "count(preceding-sibling::*)=0 and count(following-sibling::*)=0"
|
||||
when "only-of-type" then "last()=1"
|
||||
when "empty" then "not(node())"
|
||||
when "parent" then "node()"
|
||||
when "root" then "not(parent::*)"
|
||||
else
|
||||
validate_xpath_function_name(node.value.first)
|
||||
"nokogiri:#{node.value.first}(.)"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def visit_class_condition(node)
|
||||
css_class("@class", node.value.first)
|
||||
end
|
||||
|
||||
def visit_combinator(node)
|
||||
if is_of_type_pseudo_class?(node.value.last)
|
||||
"#{node.value.first&.accept(self)}][#{node.value.last.accept(self)}"
|
||||
else
|
||||
"#{node.value.first&.accept(self)} and #{node.value.last.accept(self)}"
|
||||
end
|
||||
end
|
||||
|
||||
{
|
||||
"direct_adjacent_selector" => "/following-sibling::*[1]/self::",
|
||||
"following_selector" => "/following-sibling::",
|
||||
"descendant_selector" => "//",
|
||||
"child_selector" => "/",
|
||||
}.each do |k, v|
|
||||
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
||||
def visit_#{k} node
|
||||
"\#{node.value.first.accept(self) if node.value.first}#{v}\#{node.value.last.accept(self)}"
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
def visit_conditional_selector(node)
|
||||
node.value.first.accept(self) + "[" +
|
||||
node.value.last.accept(self) + "]"
|
||||
end
|
||||
|
||||
def visit_element_name(node)
|
||||
if @doctype == DoctypeConfig::HTML5 && html5_element_name_needs_namespace_handling(node)
|
||||
# HTML5 has namespaces that should be ignored in CSS queries
|
||||
# https://github.com/sparklemotion/nokogiri/issues/2376
|
||||
if @builtins == BuiltinsConfig::ALWAYS || (@builtins == BuiltinsConfig::OPTIMAL && Nokogiri.uses_libxml?)
|
||||
if WILDCARD_NAMESPACES
|
||||
"*:#{node.value.first}"
|
||||
else
|
||||
"*[nokogiri-builtin:local-name-is('#{node.value.first}')]"
|
||||
end
|
||||
else
|
||||
"*[local-name()='#{node.value.first}']"
|
||||
end
|
||||
elsif node.value.length == 2 # has a namespace prefix
|
||||
if node.value.first.nil? # namespace prefix is empty
|
||||
node.value.last
|
||||
else
|
||||
node.value.join(":")
|
||||
end
|
||||
elsif node.value.first != "*" && @namespaces&.key?("xmlns")
|
||||
# apply the default namespace (if one is present) to a non-wildcard selector
|
||||
"xmlns:#{node.value.first}"
|
||||
else
|
||||
node.value.first
|
||||
end
|
||||
end
|
||||
|
||||
def visit_attrib_name(node)
|
||||
"@#{node.value.first}"
|
||||
end
|
||||
|
||||
def accept(node)
|
||||
node.accept(self)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_xpath_function_name(name)
|
||||
if name.start_with?("-")
|
||||
raise Nokogiri::CSS::SyntaxError, "Invalid XPath function name '#{name}'"
|
||||
end
|
||||
end
|
||||
|
||||
def html5_element_name_needs_namespace_handling(node)
|
||||
# if there is already a namespace (i.e., it is a prefixed QName), use it as normal
|
||||
node.value.length == 1 &&
|
||||
# if this is the wildcard selector "*", use it as normal
|
||||
node.value.first != "*"
|
||||
end
|
||||
|
||||
def nth(node, options = {})
|
||||
unless node.value.size == 4
|
||||
raise(ArgumentError, "expected an+b node to contain 4 tokens, but is #{node.value.inspect}")
|
||||
end
|
||||
|
||||
a, b = read_a_and_positive_b(node.value)
|
||||
position = if options[:child]
|
||||
options[:last] ? "(count(following-sibling::*)+1)" : "(count(preceding-sibling::*)+1)"
|
||||
else
|
||||
options[:last] ? "(last()-position()+1)" : "position()"
|
||||
end
|
||||
|
||||
if b.zero?
|
||||
"(#{position} mod #{a})=0"
|
||||
else
|
||||
compare = a < 0 ? "<=" : ">="
|
||||
if a.abs == 1
|
||||
"#{position}#{compare}#{b}"
|
||||
else
|
||||
"(#{position}#{compare}#{b}) and (((#{position}-#{b}) mod #{a.abs})=0)"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def read_a_and_positive_b(values)
|
||||
op = values[2].strip
|
||||
if op == "+"
|
||||
a = values[0].to_i
|
||||
b = values[3].to_i
|
||||
elsif op == "-"
|
||||
a = values[0].to_i
|
||||
b = a - (values[3].to_i % a)
|
||||
else
|
||||
raise ArgumentError, "expected an+b node to have either + or - as the operator, but is #{op.inspect}"
|
||||
end
|
||||
[a, b]
|
||||
end
|
||||
|
||||
def is_of_type_pseudo_class?(node) # rubocop:disable Naming/PredicateName
|
||||
if node.type == :PSEUDO_CLASS
|
||||
if node.value[0].is_a?(Nokogiri::CSS::Node) && (node.value[0].type == :FUNCTION)
|
||||
node.value[0].value[0]
|
||||
else
|
||||
node.value[0]
|
||||
end =~ /(nth|first|last|only)-of-type(\()?/
|
||||
end
|
||||
end
|
||||
|
||||
def css_class(hay, needle)
|
||||
if @builtins == BuiltinsConfig::ALWAYS || (@builtins == BuiltinsConfig::OPTIMAL && Nokogiri.uses_libxml?)
|
||||
# use the builtin implementation
|
||||
"nokogiri-builtin:css-class(#{hay},'#{needle}')"
|
||||
else
|
||||
# use only ordinary xpath functions
|
||||
"contains(concat(' ',normalize-space(#{hay}),' '),' #{needle} ')"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user