rexml-css_selector 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.editorconfig +8 -0
- data/.rubocop.yml +48 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +74 -0
- data/Rakefile +27 -0
- data/example/prism.rb +13 -0
- data/lib/rexml/css_selector/adapters/prism_adapter.rb +77 -0
- data/lib/rexml/css_selector/adapters/rexml_adapter.rb +77 -0
- data/lib/rexml/css_selector/ast.rb +107 -0
- data/lib/rexml/css_selector/base_adapter.rb +88 -0
- data/lib/rexml/css_selector/compiler.rb +287 -0
- data/lib/rexml/css_selector/parser.rb +430 -0
- data/lib/rexml/css_selector/pseudo_class_def.rb +19 -0
- data/lib/rexml/css_selector/queries/adjacent_query.rb +18 -0
- data/lib/rexml/css_selector/queries/attribute_matcher_query.rb +58 -0
- data/lib/rexml/css_selector/queries/attribute_presence_query.rb +20 -0
- data/lib/rexml/css_selector/queries/checked_query.rb +18 -0
- data/lib/rexml/css_selector/queries/child_query.rb +18 -0
- data/lib/rexml/css_selector/queries/class_name_query.rb +18 -0
- data/lib/rexml/css_selector/queries/descendant_query.rb +41 -0
- data/lib/rexml/css_selector/queries/disabled_query.rb +18 -0
- data/lib/rexml/css_selector/queries/empty_query.rb +17 -0
- data/lib/rexml/css_selector/queries/has_query.rb +34 -0
- data/lib/rexml/css_selector/queries/id_query.rb +18 -0
- data/lib/rexml/css_selector/queries/nested_query.rb +18 -0
- data/lib/rexml/css_selector/queries/not_query.rb +18 -0
- data/lib/rexml/css_selector/queries/nth_child_of_query.rb +40 -0
- data/lib/rexml/css_selector/queries/nth_child_query.rb +32 -0
- data/lib/rexml/css_selector/queries/nth_last_child_of_query.rb +40 -0
- data/lib/rexml/css_selector/queries/nth_last_child_query.rb +33 -0
- data/lib/rexml/css_selector/queries/nth_last_of_type_query.rb +44 -0
- data/lib/rexml/css_selector/queries/nth_of_type_query.rb +44 -0
- data/lib/rexml/css_selector/queries/one_of_query.rb +17 -0
- data/lib/rexml/css_selector/queries/only_child_query.rb +23 -0
- data/lib/rexml/css_selector/queries/only_of_type_query.rb +34 -0
- data/lib/rexml/css_selector/queries/root_query.rb +17 -0
- data/lib/rexml/css_selector/queries/scope_query.rb +17 -0
- data/lib/rexml/css_selector/queries/sibling_query.rb +21 -0
- data/lib/rexml/css_selector/queries/tag_name_type_query.rb +30 -0
- data/lib/rexml/css_selector/queries/true_query.rb +15 -0
- data/lib/rexml/css_selector/queries/universal_type_query.rb +22 -0
- data/lib/rexml/css_selector/query_context.rb +25 -0
- data/lib/rexml/css_selector/version.rb +8 -0
- data/lib/rexml/css_selector.rb +197 -0
- data/sig/rexml/css_selector.rbs +5 -0
- metadata +137 -0
@@ -0,0 +1,287 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module REXML
|
4
|
+
module CSSSelector
|
5
|
+
# CompileError is an error on compilation.
|
6
|
+
class CompileError < Error
|
7
|
+
end
|
8
|
+
|
9
|
+
class PseudoClassDef # :nodoc:
|
10
|
+
FIRST_CHILD =
|
11
|
+
PseudoClassDef.new do |cont, pseudo_class, _compiler|
|
12
|
+
raise CompileError, ":first-child must not take an argument" if pseudo_class.argument
|
13
|
+
Queries::NthChildQuery.new(cont:, a: 0, b: 1)
|
14
|
+
end
|
15
|
+
|
16
|
+
LAST_CHILD =
|
17
|
+
PseudoClassDef.new do |cont, pseudo_class, _compiler|
|
18
|
+
raise CompileError, ":last-child must not take an argument" if pseudo_class.argument
|
19
|
+
Queries::NthLastChildQuery.new(cont:, a: 0, b: 1)
|
20
|
+
end
|
21
|
+
|
22
|
+
ONLY_CHILD =
|
23
|
+
PseudoClassDef.new do |cont, pseudo_class, _compiler|
|
24
|
+
raise CompileError, ":only-child must not take an argument" if pseudo_class.argument
|
25
|
+
Queries::OnlyChildQuery.new(cont:)
|
26
|
+
end
|
27
|
+
|
28
|
+
NTH_CHILD =
|
29
|
+
PseudoClassDef.new(:nth_of_selector_list) do |cont, pseudo_class, compiler|
|
30
|
+
raise CompileError, ":nth-child must take an argument" unless pseudo_class.argument
|
31
|
+
case pseudo_class.argument
|
32
|
+
in NthOfSelectorList[nth:, selector_list: nil]
|
33
|
+
a, b = compiler.nth_value(nth)
|
34
|
+
Queries::NthChildQuery.new(cont:, a:, b:)
|
35
|
+
in NthOfSelectorList[nth:, selector_list:]
|
36
|
+
if selector_list.selectors.any? { _1.is_a?(ComplexSelector) }
|
37
|
+
raise CompileError, ":nth-child agument must not take a complex selector"
|
38
|
+
end
|
39
|
+
a, b = compiler.nth_value(nth)
|
40
|
+
query = compiler.compile(selector_list)
|
41
|
+
Queries::NthChildOfQuery.new(cont:, a:, b:, query:)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
NTH_LAST_CHILD =
|
46
|
+
PseudoClassDef.new(:nth_of_selector_list) do |cont, pseudo_class, compiler|
|
47
|
+
raise CompileError, ":nth-last-child must take an argument" unless pseudo_class.argument
|
48
|
+
case pseudo_class.argument
|
49
|
+
in NthOfSelectorList[nth:, selector_list: nil]
|
50
|
+
a, b = compiler.nth_value(nth)
|
51
|
+
Queries::NthLastChildQuery.new(cont:, a:, b:)
|
52
|
+
in NthOfSelectorList[nth:, selector_list:]
|
53
|
+
if selector_list.selectors.any? { _1.is_a?(ComplexSelector) }
|
54
|
+
raise CompileError, ":nth-last-child agument must not take a complex selector"
|
55
|
+
end
|
56
|
+
a, b = compiler.nth_value(nth)
|
57
|
+
query = compiler.compile(selector_list)
|
58
|
+
Queries::NthLastChildOfQuery.new(cont:, a:, b:, query:)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
FIRST_OF_TYPE =
|
63
|
+
PseudoClassDef.new do |cont, pseudo_class, _compiler|
|
64
|
+
raise CompileError, ":first-of-type must not take an argument" if pseudo_class.argument
|
65
|
+
Queries::NthOfTypeQuery.new(cont:, a: 0, b: 1)
|
66
|
+
end
|
67
|
+
|
68
|
+
LAST_OF_TYPE =
|
69
|
+
PseudoClassDef.new do |cont, pseudo_class, _compiler|
|
70
|
+
raise CompileError, ":last-of-type must not take an argument" if pseudo_class.argument
|
71
|
+
Queries::NthLastOfTypeQuery.new(cont:, a: 0, b: 1)
|
72
|
+
end
|
73
|
+
|
74
|
+
ONLY_OF_TYPE =
|
75
|
+
PseudoClassDef.new do |cont, pseudo_class, _compiler|
|
76
|
+
raise CompileError, ":only-of-type must not take an argument" if pseudo_class.argument
|
77
|
+
Queries::OnlyOfTypeQuery.new(cont:)
|
78
|
+
end
|
79
|
+
|
80
|
+
NTH_OF_TYPE =
|
81
|
+
PseudoClassDef.new(:nth) do |cont, pseudo_class, compiler|
|
82
|
+
raise CompileError, ":nth-of-type must take an argument" unless pseudo_class.argument
|
83
|
+
a, b = compiler.nth_value(pseudo_class.argument)
|
84
|
+
Queries::NthOfTypeQuery.new(cont:, a:, b:)
|
85
|
+
end
|
86
|
+
|
87
|
+
NTH_LAST_OF_TYPE =
|
88
|
+
PseudoClassDef.new(:nth) do |cont, pseudo_class, compiler|
|
89
|
+
raise CompileError, ":nth-last-of-type must take an argument" unless pseudo_class.argument
|
90
|
+
a, b = compiler.nth_value(pseudo_class.argument)
|
91
|
+
Queries::NthLastOfTypeQuery.new(cont:, a:, b:)
|
92
|
+
end
|
93
|
+
|
94
|
+
ROOT =
|
95
|
+
PseudoClassDef.new do |cont, pseudo_class, _compiler|
|
96
|
+
raise CompileError, ":root must not take an argument" if pseudo_class.argument
|
97
|
+
Queries::RootQuery.new(cont:)
|
98
|
+
end
|
99
|
+
|
100
|
+
IS =
|
101
|
+
PseudoClassDef.new(:selector_list) do |cont, pseudo_class, compiler|
|
102
|
+
raise CompileError, ":is must take an argument" unless pseudo_class.argument
|
103
|
+
selector_list = pseudo_class.argument
|
104
|
+
if selector_list.selectors.any? { _1.is_a?(ComplexSelector) }
|
105
|
+
raise CompileError, ":is agument must not take a complex selector"
|
106
|
+
end
|
107
|
+
query = compiler.compile(selector_list)
|
108
|
+
Queries::NestedQuery.new(cont:, query:)
|
109
|
+
end
|
110
|
+
|
111
|
+
WHERE =
|
112
|
+
PseudoClassDef.new(:selector_list) do |cont, pseudo_class, compiler|
|
113
|
+
raise CompileError, ":where must take an argument" unless pseudo_class.argument
|
114
|
+
selector_list = pseudo_class.argument
|
115
|
+
if selector_list.selectors.any? { _1.is_a?(ComplexSelector) }
|
116
|
+
raise CompileError, ":where agument must not take a complex selector"
|
117
|
+
end
|
118
|
+
query = compiler.compile(selector_list)
|
119
|
+
Queries::NestedQuery.new(cont:, query:)
|
120
|
+
end
|
121
|
+
|
122
|
+
NOT =
|
123
|
+
PseudoClassDef.new(:selector_list) do |cont, pseudo_class, compiler|
|
124
|
+
raise CompileError, ":not must take an argument" unless pseudo_class.argument
|
125
|
+
selector_list = pseudo_class.argument
|
126
|
+
if selector_list.selectors.any? { _1.is_a?(ComplexSelector) }
|
127
|
+
raise CompileError, ":not agument must not take a complex selector"
|
128
|
+
end
|
129
|
+
query = compiler.compile(selector_list)
|
130
|
+
Queries::NotQuery.new(cont:, query:)
|
131
|
+
end
|
132
|
+
|
133
|
+
SCOPE =
|
134
|
+
PseudoClassDef.new do |cont, pseudo_class, _compiler|
|
135
|
+
raise CompileError, ":scope must not take an argument" if pseudo_class.argument
|
136
|
+
Queries::ScopeQuery.new(cont:)
|
137
|
+
end
|
138
|
+
|
139
|
+
HAS =
|
140
|
+
PseudoClassDef.new(:relative_selector_list) do |cont, pseudo_class, compiler|
|
141
|
+
raise CompileError, ":has must take an argument" unless pseudo_class.argument
|
142
|
+
relative_selector_list = pseudo_class.argument
|
143
|
+
needs_parent = false
|
144
|
+
selectors =
|
145
|
+
relative_selector_list.selectors.map do |relative_selector|
|
146
|
+
scope =
|
147
|
+
CompoundSelector[
|
148
|
+
type: nil,
|
149
|
+
subclasses: [PseudoClass[name: "scope", argument: nil]],
|
150
|
+
pseudo_elements: []
|
151
|
+
]
|
152
|
+
case relative_selector.combinator
|
153
|
+
when :sibling, :adjacent
|
154
|
+
needs_parent = true
|
155
|
+
end
|
156
|
+
ComplexSelector[left: scope, combinator: relative_selector.combinator, right: relative_selector.right]
|
157
|
+
end
|
158
|
+
query = compiler.compile(SelectorList[selectors:])
|
159
|
+
Queries::HasQuery.new(cont:, query:, needs_parent:)
|
160
|
+
end
|
161
|
+
|
162
|
+
EMPTY =
|
163
|
+
PseudoClassDef.new do |cont, pseudo_class, _compiler|
|
164
|
+
raise CompileError, ":empty must not take an argument" if pseudo_class.argument
|
165
|
+
Queries::EmptyQuery.new(cont:)
|
166
|
+
end
|
167
|
+
|
168
|
+
CHECKED =
|
169
|
+
PseudoClassDef.new do |cont, pseudo_class, _compiler|
|
170
|
+
raise CompileError, ":checked must not take an argument" if pseudo_class.argument
|
171
|
+
Queries::CheckedQuery.new(cont:)
|
172
|
+
end
|
173
|
+
|
174
|
+
DISABLED =
|
175
|
+
PseudoClassDef.new do |cont, pseudo_class, _compiler|
|
176
|
+
raise CompileError, ":disabled must not take an argument" if pseudo_class.argument
|
177
|
+
Queries::DisabledQuery.new(cont:)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Compiler is a compiler from selectors to queries.
|
182
|
+
class Compiler
|
183
|
+
def initialize(**config)
|
184
|
+
@config = config
|
185
|
+
@config[:pseudo_classes] ||= {}
|
186
|
+
end
|
187
|
+
|
188
|
+
def compile(selector_list)
|
189
|
+
compile_selector_list(selector_list)
|
190
|
+
end
|
191
|
+
|
192
|
+
def namespace_name(namespace)
|
193
|
+
case namespace
|
194
|
+
in Namespace[name:]
|
195
|
+
name
|
196
|
+
in UniversalNamespace[] | nil
|
197
|
+
nil
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def nth_value(nth)
|
202
|
+
case nth
|
203
|
+
in Nth[a:, b:]
|
204
|
+
[a, b]
|
205
|
+
in Odd[]
|
206
|
+
[2, 1]
|
207
|
+
in Even[]
|
208
|
+
[2, 0]
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
private
|
213
|
+
|
214
|
+
def compile_selector_list(selector_list)
|
215
|
+
queries = selector_list.selectors.map { compile_complex_selector(_1) }
|
216
|
+
return queries.first if queries.size == 1
|
217
|
+
Queries::OneOfQuery.new(conts: queries)
|
218
|
+
end
|
219
|
+
|
220
|
+
def compile_complex_selector(selector)
|
221
|
+
cont = Queries::TrueQuery::INSTANCE
|
222
|
+
loop do
|
223
|
+
return compile_compound_selector(cont, selector) if selector.is_a?(CompoundSelector)
|
224
|
+
|
225
|
+
cont = compile_compound_selector(cont, selector.left)
|
226
|
+
cont = compile_combinator(cont, selector.combinator)
|
227
|
+
selector = selector.right
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def compile_compound_selector(cont, selector)
|
232
|
+
cont = compile_type_selector(cont, selector.type) if selector.type
|
233
|
+
|
234
|
+
selector.subclasses.each { |subclass| cont = compile_subclass_selector(cont, subclass) }
|
235
|
+
|
236
|
+
raise CompileError, "pseudo elements are not supported" unless selector.pseudo_elements.empty?
|
237
|
+
|
238
|
+
cont
|
239
|
+
end
|
240
|
+
|
241
|
+
def compile_type_selector(cont, type)
|
242
|
+
case type
|
243
|
+
in TagNameType[namespace:, tag_name:]
|
244
|
+
namespace = namespace_name(namespace)
|
245
|
+
Queries::TagNameTypeQuery.new(cont:, tag_name:, namespace:)
|
246
|
+
in UniversalType[namespace:]
|
247
|
+
namespace = namespace_name(namespace)
|
248
|
+
Queries::UniversalTypeQuery.new(cont:, namespace:)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def compile_subclass_selector(cont, subclass)
|
253
|
+
case subclass
|
254
|
+
in ClassName[name:]
|
255
|
+
Queries::ClassNameQuery.new(cont:, name:)
|
256
|
+
in Id[name:]
|
257
|
+
Queries::IdQuery.new(cont:, name:)
|
258
|
+
in Attribute[namespace:, name:, matcher: nil, value: nil, modifier: nil]
|
259
|
+
namespace = namespace_name(namespace)
|
260
|
+
Queries::AttributePresenceQuery.new(cont:, name:, namespace:)
|
261
|
+
in Attribute[namespace:, name:, matcher:, value:, modifier:]
|
262
|
+
namespace = namespace_name(namespace)
|
263
|
+
Queries::AttributeMatcherQuery.new(cont:, name:, namespace:, matcher:, value:, modifier:)
|
264
|
+
in PseudoClass[name:] => pseudo_class
|
265
|
+
pseudo_class_def = @config[:pseudo_classes][name]
|
266
|
+
raise CompileError, "undefined pseudo class ':#{name}'" unless pseudo_class_def
|
267
|
+
pseudo_class_def.compile(cont, pseudo_class, self)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def compile_combinator(cont, combinator)
|
272
|
+
case combinator
|
273
|
+
in :descendant
|
274
|
+
Queries::DescendantQuery.new(cont:)
|
275
|
+
in :sibling
|
276
|
+
Queries::SiblingQuery.new(cont:)
|
277
|
+
in :adjacent
|
278
|
+
Queries::AdjacentQuery.new(cont:)
|
279
|
+
in :child
|
280
|
+
Queries::ChildQuery.new(cont:)
|
281
|
+
in :column
|
282
|
+
raise CompileError, "column combinator is not supported"
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|