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