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,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