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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +8 -0
  3. data/.rubocop.yml +48 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +74 -0
  7. data/Rakefile +27 -0
  8. data/example/prism.rb +13 -0
  9. data/lib/rexml/css_selector/adapters/prism_adapter.rb +77 -0
  10. data/lib/rexml/css_selector/adapters/rexml_adapter.rb +77 -0
  11. data/lib/rexml/css_selector/ast.rb +107 -0
  12. data/lib/rexml/css_selector/base_adapter.rb +88 -0
  13. data/lib/rexml/css_selector/compiler.rb +287 -0
  14. data/lib/rexml/css_selector/parser.rb +430 -0
  15. data/lib/rexml/css_selector/pseudo_class_def.rb +19 -0
  16. data/lib/rexml/css_selector/queries/adjacent_query.rb +18 -0
  17. data/lib/rexml/css_selector/queries/attribute_matcher_query.rb +58 -0
  18. data/lib/rexml/css_selector/queries/attribute_presence_query.rb +20 -0
  19. data/lib/rexml/css_selector/queries/checked_query.rb +18 -0
  20. data/lib/rexml/css_selector/queries/child_query.rb +18 -0
  21. data/lib/rexml/css_selector/queries/class_name_query.rb +18 -0
  22. data/lib/rexml/css_selector/queries/descendant_query.rb +41 -0
  23. data/lib/rexml/css_selector/queries/disabled_query.rb +18 -0
  24. data/lib/rexml/css_selector/queries/empty_query.rb +17 -0
  25. data/lib/rexml/css_selector/queries/has_query.rb +34 -0
  26. data/lib/rexml/css_selector/queries/id_query.rb +18 -0
  27. data/lib/rexml/css_selector/queries/nested_query.rb +18 -0
  28. data/lib/rexml/css_selector/queries/not_query.rb +18 -0
  29. data/lib/rexml/css_selector/queries/nth_child_of_query.rb +40 -0
  30. data/lib/rexml/css_selector/queries/nth_child_query.rb +32 -0
  31. data/lib/rexml/css_selector/queries/nth_last_child_of_query.rb +40 -0
  32. data/lib/rexml/css_selector/queries/nth_last_child_query.rb +33 -0
  33. data/lib/rexml/css_selector/queries/nth_last_of_type_query.rb +44 -0
  34. data/lib/rexml/css_selector/queries/nth_of_type_query.rb +44 -0
  35. data/lib/rexml/css_selector/queries/one_of_query.rb +17 -0
  36. data/lib/rexml/css_selector/queries/only_child_query.rb +23 -0
  37. data/lib/rexml/css_selector/queries/only_of_type_query.rb +34 -0
  38. data/lib/rexml/css_selector/queries/root_query.rb +17 -0
  39. data/lib/rexml/css_selector/queries/scope_query.rb +17 -0
  40. data/lib/rexml/css_selector/queries/sibling_query.rb +21 -0
  41. data/lib/rexml/css_selector/queries/tag_name_type_query.rb +30 -0
  42. data/lib/rexml/css_selector/queries/true_query.rb +15 -0
  43. data/lib/rexml/css_selector/queries/universal_type_query.rb +22 -0
  44. data/lib/rexml/css_selector/query_context.rb +25 -0
  45. data/lib/rexml/css_selector/version.rb +8 -0
  46. data/lib/rexml/css_selector.rb +197 -0
  47. data/sig/rexml/css_selector.rbs +5 -0
  48. 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