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