rexml-css_selector 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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