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,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class HasQuery
7
+ def initialize(cont:, query:, needs_parent:)
8
+ @cont = cont
9
+ @query = query
10
+ @needs_parent = needs_parent
11
+ end
12
+
13
+ def call(node, context)
14
+ matched = false
15
+ context.scoped(node) do
16
+ base = node
17
+ base = context.adapter.get_parent_node(base) if @needs_parent
18
+ context
19
+ .adapter
20
+ .each_recursive_node(base) do |child|
21
+ next if node == child
22
+ if @query.call(child, context)
23
+ matched = true
24
+ break
25
+ end
26
+ end
27
+ end
28
+
29
+ matched && @cont.call(node, context)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class IdQuery
7
+ def initialize(cont:, name:)
8
+ @cont = cont
9
+ @name = name
10
+ end
11
+
12
+ def call(node, context)
13
+ context.adapter.get_id(node) == @name && @cont.call(node, context)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class NestedQuery
7
+ def initialize(cont:, query:)
8
+ @cont = cont
9
+ @query = query
10
+ end
11
+
12
+ def call(node, context)
13
+ @query.call(node, context) && @cont.call(node, context)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class NotQuery
7
+ def initialize(cont:, query:)
8
+ @cont = cont
9
+ @query = query
10
+ end
11
+
12
+ def call(node, context)
13
+ !@query.call(node, context) && @cont.call(node, context)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class NthChildOfQuery
7
+ def initialize(cont:, a:, b:, query:)
8
+ @cont = cont
9
+ @a = a
10
+ @b = b
11
+ @query = query
12
+ end
13
+
14
+ def call(node, context)
15
+ return false unless context.adapter.element?(node)
16
+
17
+ parent = context.adapter.get_parent_node(node)
18
+ return false unless parent
19
+
20
+ matched_children = context.cache[[object_id, parent.object_id]]
21
+ unless matched_children
22
+ children = context.adapter.get_children_elements(parent)
23
+ matched_children = children.filter { @query.call(_1, context) }
24
+ context.cache[[object_id, parent.object_id]] = matched_children
25
+ end
26
+
27
+ index = matched_children.index(node)
28
+ return false unless index
29
+ index += 1
30
+
31
+ if @a.zero?
32
+ index == @b && @cont.call(node, context)
33
+ else
34
+ ((index - @b) % @a).zero? && (index - @b) / @a >= 0 && @cont.call(node, context)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class NthChildQuery
7
+ def initialize(cont:, a:, b:)
8
+ @cont = cont
9
+ @a = a
10
+ @b = b
11
+ end
12
+
13
+ def call(node, context)
14
+ return false unless context.adapter.element?(node)
15
+
16
+ parent = context.adapter.get_parent_node(node)
17
+ return false unless parent
18
+
19
+ index = context.adapter.get_element_index(parent, node)
20
+ return false unless index
21
+ index += 1
22
+
23
+ if @a.zero?
24
+ index == @b && @cont.call(node, context)
25
+ else
26
+ ((index - @b) % @a).zero? && (index - @b) / @a >= 0 && @cont.call(node, context)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class NthLastChildOfQuery
7
+ def initialize(cont:, a:, b:, query:)
8
+ @cont = cont
9
+ @a = a
10
+ @b = b
11
+ @query = query
12
+ end
13
+
14
+ def call(node, context)
15
+ return false unless context.adapter.element?(node)
16
+
17
+ parent = context.adapter.get_parent_node(node)
18
+ return false unless parent
19
+
20
+ matched_children = context.cache[[object_id, parent.object_id]]
21
+ unless matched_children
22
+ children = context.adapter.get_children_elements(parent)
23
+ matched_children = children.filter { @query.call(_1, context) }
24
+ context.cache[[object_id, parent.object_id]] = matched_children
25
+ end
26
+
27
+ index = matched_children.index(node)
28
+ return false unless index
29
+ index = matched_children.size - index
30
+
31
+ if @a.zero?
32
+ index == @b && @cont.call(node, context)
33
+ else
34
+ ((index - @b) % @a).zero? && (index - @b) / @a >= 0 && @cont.call(node, context)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class NthLastChildQuery
7
+ def initialize(cont:, a:, b:)
8
+ @cont = cont
9
+ @a = a
10
+ @b = b
11
+ end
12
+
13
+ def call(node, context)
14
+ return false unless context.adapter.element?(node)
15
+
16
+ parent = context.adapter.get_parent_node(node)
17
+ return false unless parent
18
+
19
+ children_size = context.adapter.get_children_elements(parent).size
20
+ index = context.adapter.get_element_index(parent, node)
21
+ return false unless index
22
+ index = children_size - index
23
+
24
+ if @a.zero?
25
+ index == @b && @cont.call(node, context)
26
+ else
27
+ ((index - @b) % @a).zero? && (index - @b) / @a >= 0 && @cont.call(node, context)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class NthLastOfTypeQuery
7
+ def initialize(cont:, a:, b:)
8
+ @cont = cont
9
+ @a = a
10
+ @b = b
11
+ end
12
+
13
+ def call(node, context)
14
+ return false unless context.adapter.element?(node)
15
+
16
+ parent = context.adapter.get_parent_node(node)
17
+ return false unless parent
18
+
19
+ insensitive = context.options[:tag_name_case] == :insensitive
20
+ tag_name = context.adapter.get_tag_name(node)
21
+ tag_name = tag_name.downcase(:ascii) if insensitive
22
+
23
+ children = context.adapter.get_children_elements(parent)
24
+ children =
25
+ children.filter do |child|
26
+ child_tag_name = context.adapter.get_tag_name(child)
27
+ child_tag_name = child_tag_name.downcase(:ascii) if insensitive
28
+ tag_name == child_tag_name
29
+ end
30
+
31
+ index = children.index(node)
32
+ return false unless index
33
+ index = children.size - index
34
+
35
+ if @a.zero?
36
+ index == @b && @cont.call(node, context)
37
+ else
38
+ ((index - @b) % @a).zero? && (index - @b) / @a >= 0 && @cont.call(node, context)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class NthOfTypeQuery
7
+ def initialize(cont:, a:, b:)
8
+ @cont = cont
9
+ @a = a
10
+ @b = b
11
+ end
12
+
13
+ def call(node, context)
14
+ return false unless context.adapter.element?(node)
15
+
16
+ parent = context.adapter.get_parent_node(node)
17
+ return false unless parent
18
+
19
+ insensitive = context.options[:tag_name_case] == :insensitive
20
+ tag_name = context.adapter.get_tag_name(node)
21
+ tag_name = tag_name.downcase(:ascii) if insensitive
22
+
23
+ children = context.adapter.get_children_elements(parent)
24
+ children =
25
+ children.filter do |child|
26
+ child_tag_name = context.adapter.get_tag_name(child)
27
+ child_tag_name = child_tag_name.downcase(:ascii) if insensitive
28
+ tag_name == child_tag_name
29
+ end
30
+
31
+ index = children.index(node)
32
+ return false unless index
33
+ index += 1
34
+
35
+ if @a.zero?
36
+ index == @b && @cont.call(node, context)
37
+ else
38
+ ((index - @b) % @a).zero? && (index - @b) / @a >= 0 && @cont.call(node, context)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class OneOfQuery
7
+ def initialize(conts:)
8
+ @conts = conts
9
+ end
10
+
11
+ def call(node, context)
12
+ @conts.any? { |cont| cont.call(node, context) }
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class OnlyChildQuery
7
+ def initialize(cont:)
8
+ @cont = cont
9
+ end
10
+
11
+ def call(node, context)
12
+ return false unless context.adapter.element?(node)
13
+
14
+ parent = context.adapter.get_parent_node(node)
15
+ return false unless parent
16
+
17
+ children = context.adapter.get_children_elements(parent)
18
+ children.size == 1 && @cont.call(node, context)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class OnlyOfTypeQuery
7
+ def initialize(cont:)
8
+ @cont = cont
9
+ end
10
+
11
+ def call(node, context)
12
+ return false unless context.adapter.element?(node)
13
+
14
+ parent = context.adapter.get_parent_node(node)
15
+ return false unless parent
16
+
17
+ insensitive = context.options[:tag_name_case] == :insensitive
18
+ tag_name = context.adapter.get_tag_name(node)
19
+ tag_name = tag_name.downcase(:ascii) if insensitive
20
+
21
+ children = context.adapter.get_children_elements(parent)
22
+ children =
23
+ children.filter do |child|
24
+ child_tag_name = context.adapter.get_tag_name(child)
25
+ child_tag_name = child_tag_name.downcase(:ascii) if insensitive
26
+ tag_name == child_tag_name
27
+ end
28
+
29
+ children.size == 1 && @cont.call(node, context)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class RootQuery
7
+ def initialize(cont:)
8
+ @cont = cont
9
+ end
10
+
11
+ def call(node, context)
12
+ context.adapter.element?(node) && context.adapter.root?(node) && @cont.call(node, context)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class ScopeQuery
7
+ def initialize(cont:)
8
+ @cont = cont
9
+ end
10
+
11
+ def call(node, context)
12
+ node == context.scope && @cont.call(node, context)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class SiblingQuery
7
+ def initialize(cont:)
8
+ @cont = cont
9
+ end
10
+
11
+ def call(node, context)
12
+ while (node = context.adapter.get_previous_sibling_element(node))
13
+ return true if context.adapter.element?(node) && @cont.call(node, context)
14
+ end
15
+
16
+ false
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class TagNameTypeQuery
7
+ def initialize(cont:, tag_name:, namespace:)
8
+ @tag_name = tag_name
9
+ @namespace = namespace
10
+ @cont = cont
11
+ end
12
+
13
+ def call(node, context)
14
+ return false unless context.adapter.element?(node)
15
+
16
+ return false if @namespace && context.adapter.get_namespace(node) != @namespace
17
+
18
+ tag_name = context.adapter.get_tag_name(node)
19
+ case context.options[:tag_name_case]
20
+ in :sensitive
21
+ tag_name == @tag_name && @cont.call(node, context)
22
+ in :insensitive
23
+ tag_name = tag_name.downcase(:ascii)
24
+ tag_name == @tag_name.downcase(:ascii) && @cont.call(node, context)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class TrueQuery
7
+ def call(_node, _context)
8
+ true
9
+ end
10
+
11
+ INSTANCE = new
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ module Queries
6
+ class UniversalTypeQuery
7
+ def initialize(cont:, namespace:)
8
+ @namespace = namespace
9
+ @cont = cont
10
+ end
11
+
12
+ def call(node, context)
13
+ return false unless context.adapter.element?(node)
14
+
15
+ return false if @namespace && context.adapter.get_namespace(node) != @namespace
16
+
17
+ @cont.call(node, context)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ # QueryContext is a context on matching CSS selector.
6
+ class QueryContext
7
+ def initialize(scope:, substitutions:, adapter:, options:)
8
+ @scope = scope
9
+ @substitutions = substitutions
10
+ @adapter = adapter
11
+ @options = options
12
+ @cache = {}
13
+ end
14
+
15
+ attr_reader :scope, :substitutions, :adapter, :cache, :options
16
+
17
+ def scoped(new_scope)
18
+ old_scope = @scope
19
+ @scope = new_scope
20
+ yield
21
+ @scope = old_scope
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module REXML
4
+ module CSSSelector
5
+ # A version number of this library.
6
+ VERSION = "0.1.0"
7
+ end
8
+ end