synvert-core 1.1.1 → 1.3.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 +4 -4
- data/CHANGELOG.md +18 -0
- data/Gemfile +9 -0
- data/lib/synvert/core/array_ext.rb +9 -2
- data/lib/synvert/core/node_ext.rb +14 -2
- data/lib/synvert/core/node_query/compiler/array.rb +1 -1
- data/lib/synvert/core/node_query/compiler/attribute.rb +6 -18
- data/lib/synvert/core/node_query/compiler/comparable.rb +26 -19
- data/lib/synvert/core/node_query/compiler/expression.rb +22 -52
- data/lib/synvert/core/node_query/compiler/identifier.rb +1 -1
- data/lib/synvert/core/node_query/compiler/regexp.rb +2 -2
- data/lib/synvert/core/node_query/compiler/selector.rb +116 -31
- data/lib/synvert/core/node_query/compiler/simple_selector.rb +29 -0
- data/lib/synvert/core/node_query/compiler/string.rb +1 -1
- data/lib/synvert/core/node_query/compiler.rb +1 -0
- data/lib/synvert/core/node_query/lexer.rex +27 -17
- data/lib/synvert/core/node_query/lexer.rex.rb +53 -29
- data/lib/synvert/core/node_query/parser.racc.rb +119 -315
- data/lib/synvert/core/node_query/parser.y +20 -42
- data/lib/synvert/core/node_query.rb +7 -7
- data/lib/synvert/core/version.rb +1 -1
- data/spec/synvert/core/node_ext_spec.rb +7 -5
- data/spec/synvert/core/node_query/lexer_spec.rb +76 -50
- data/spec/synvert/core/node_query/parser_spec.rb +181 -114
- data/spec/synvert/core/rewriter/instance_spec.rb +3 -3
- data/spec/synvert/core/rewriter/scope/query_scope_spec.rb +3 -3
- data/synvert-core-ruby.gemspec +0 -10
- metadata +3 -128
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 56b98032247ef28e548b34dfd8b2b49bad8744227cd61b4f300c7111b701ff91
|
4
|
+
data.tar.gz: ea75a1e4f442041665dbd9babdc2f7e4b55c4c78c87facef856e903f2d8c96d2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 905c1f393a1702df43300432e210784790a30dff9a7bb0c798c742ec372a65ad8487b2097333f57afb1798c566f554ab9096ae92db8cd2541e5bb03769009882
|
7
|
+
data.tar.gz: bd03f1c285c5b5b42628df0b3c8777d6167d980c8c3e3b2f92badfc040b2bef5fe2bef3b848b5a6a166002a5e68f8f4f287ac6ee8fadc72a3ef6706fe191c821
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,23 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 1.3.0 (2022-05-12)
|
4
|
+
|
5
|
+
* Support `*=`, `^=` and `$=` operators
|
6
|
+
* Simplify RELATIONSHIP parser
|
7
|
+
* Rewrite compiler, let the selector to query nodes
|
8
|
+
|
9
|
+
## 1.2.1 (2022-05-01)
|
10
|
+
|
11
|
+
* Selector always after a node type in NQL
|
12
|
+
* Define `pairs` method for `hash` ndoe
|
13
|
+
|
14
|
+
## 1.2.0 (2022-04-29)
|
15
|
+
|
16
|
+
* Remove comma in NQL array value
|
17
|
+
* Parse pseduo class without selector in NQL
|
18
|
+
* Parse multiple goto scope in NQL
|
19
|
+
* Parse `nil?` in NQL
|
20
|
+
|
3
21
|
## 1.1.1 (2022-04-27)
|
4
22
|
|
5
23
|
* Parse empty string properly in node query language
|
data/Gemfile
CHANGED
@@ -13,7 +13,7 @@ class Array
|
|
13
13
|
return child_direct_child_node if child_direct_child_node
|
14
14
|
|
15
15
|
raise Synvert::Core::MethodNotSupported,
|
16
|
-
"child_node_by_name is not handled for #{
|
16
|
+
"child_node_by_name is not handled for #{debug_info}, child_name: #{child_name}"
|
17
17
|
end
|
18
18
|
|
19
19
|
# Get the source range of child node.
|
@@ -35,7 +35,14 @@ class Array
|
|
35
35
|
)
|
36
36
|
else
|
37
37
|
raise Synvert::Core::MethodNotSupported,
|
38
|
-
"child_node_range is not handled for #{
|
38
|
+
"child_node_range is not handled for #{debug_info}, child_name: #{child_name}"
|
39
39
|
end
|
40
40
|
end
|
41
|
+
|
42
|
+
# Return the debug info.
|
43
|
+
#
|
44
|
+
# @return [String] file, line, source and node.
|
45
|
+
def debug_info
|
46
|
+
map(&:debug_info).join("\n")
|
47
|
+
end
|
41
48
|
end
|
@@ -93,9 +93,21 @@ module Parser::AST
|
|
93
93
|
end
|
94
94
|
|
95
95
|
# Dyamically defined method
|
96
|
-
# caller, key, left_value, message, name, parent_class, parent_const, receivr, rgith_value and value.
|
96
|
+
# caller, key, left_value, message, name, pairs, parent_class, parent_const, receivr, rgith_value and value.
|
97
97
|
# based on const TYPE_CHILDREN.
|
98
|
-
%i[
|
98
|
+
%i[
|
99
|
+
caller
|
100
|
+
key
|
101
|
+
left_value
|
102
|
+
message
|
103
|
+
name
|
104
|
+
pairs
|
105
|
+
parent_class
|
106
|
+
parent_const
|
107
|
+
receiver
|
108
|
+
right_value
|
109
|
+
value
|
110
|
+
].each do |method_name|
|
99
111
|
define_method(method_name) do
|
100
112
|
index = TYPE_CHILDREN[type]&.index(method_name)
|
101
113
|
return children[index] if index
|
@@ -7,7 +7,7 @@ module Synvert::Core::NodeQuery::Compiler
|
|
7
7
|
# @param key [String] the key
|
8
8
|
# @param value the value can be any class implement {Synvert::Core::NodeQuery::Compiler::Comparable}
|
9
9
|
# @param operator [Symbol] the operator
|
10
|
-
def initialize(key:, value:, operator:
|
10
|
+
def initialize(key:, value:, operator: '==')
|
11
11
|
@key = key
|
12
12
|
@value = value
|
13
13
|
@operator = operator
|
@@ -23,25 +23,13 @@ module Synvert::Core::NodeQuery::Compiler
|
|
23
23
|
|
24
24
|
def to_s
|
25
25
|
case @operator
|
26
|
-
when
|
27
|
-
"#{@key}
|
28
|
-
when
|
29
|
-
"#{@key}=~#{@value}"
|
30
|
-
when :!~
|
31
|
-
"#{@key}!~#{@value}"
|
32
|
-
when :>
|
33
|
-
"#{@key}>#{@value}"
|
34
|
-
when :>=
|
35
|
-
"#{@key}>=#{@value}"
|
36
|
-
when :<
|
37
|
-
"#{@key}<#{@value}"
|
38
|
-
when :<=
|
39
|
-
"#{@key}<=#{@value}"
|
40
|
-
when :in
|
26
|
+
when '^=', '$=', '*=', '!=', '=~', '!~', '>=', '>', '<=', '<'
|
27
|
+
"#{@key}#{@operator}#{@value}"
|
28
|
+
when 'in'
|
41
29
|
"#{@key} in (#{@value})"
|
42
|
-
when
|
30
|
+
when 'not_in'
|
43
31
|
"#{@key} not in (#{@value})"
|
44
|
-
when
|
32
|
+
when 'includes'
|
45
33
|
"#{@key} includes #{@value}"
|
46
34
|
else
|
47
35
|
"#{@key}=#{@value}"
|
@@ -3,52 +3,59 @@
|
|
3
3
|
module Synvert::Core::NodeQuery::Compiler
|
4
4
|
# Compare acutal value with expected value.
|
5
5
|
module Comparable
|
6
|
-
SIMPLE_VALID_OPERATORS = [
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
SIMPLE_VALID_OPERATORS = ['==', '!=', 'includes']
|
7
|
+
STRING_VALID_OPERATORS = ['==', '!=', '^=', '$=', '*=', 'includes']
|
8
|
+
NUMBER_VALID_OPERATORS = ['==', '!=', '>', '>=', '<', '<=', 'includes']
|
9
|
+
ARRAY_VALID_OPERATORS = ['==', '!=', 'in', 'not_in']
|
10
|
+
REGEXP_VALID_OPERATORS = ['=~', '!~']
|
10
11
|
|
11
12
|
# Check if the actual value matches the expected value.
|
12
13
|
#
|
13
14
|
# @param node [Parser::AST::Node] node to calculate actual value
|
14
|
-
# @param operator [Symbol] operator to compare with expected value, operator can be <code
|
15
|
+
# @param operator [Symbol] operator to compare with expected value, operator can be <code>'=='</code>, <code>'!='</code>, <code>'>'</code>, <code>'>='</code>, <code>'<'</code>, <code>'<='</code>, <code>'includes'</code>, <code>'in'</code>, <code>'not_in'</code>, <code>'=~'</code>, <code>'!~'</code>
|
15
16
|
# @return [Boolean] true if actual value matches the expected value
|
16
17
|
# @raise [Synvert::Core::NodeQuery::Compiler::InvalidOperatorError] if operator is invalid
|
17
18
|
def match?(node, operator)
|
18
19
|
raise InvalidOperatorError, "invalid operator #{operator}" unless valid_operator?(operator)
|
19
20
|
|
20
21
|
case operator
|
21
|
-
when
|
22
|
+
when '!='
|
22
23
|
if expected_value.is_a?(::Array)
|
23
24
|
actual = actual_value(node)
|
24
25
|
!actual.is_a?(::Array) || actual.size != expected_value.size ||
|
25
|
-
actual.zip(expected_value).any? { |actual_node, expected_node| expected_node.match?(actual_node,
|
26
|
+
actual.zip(expected_value).any? { |actual_node, expected_node| expected_node.match?(actual_node, '!=') }
|
26
27
|
else
|
27
28
|
actual_value(node) != expected_value
|
28
29
|
end
|
29
|
-
when
|
30
|
+
when '=~'
|
30
31
|
actual_value(node) =~ expected_value
|
31
|
-
when
|
32
|
+
when '!~'
|
32
33
|
actual_value(node) !~ expected_value
|
33
|
-
when
|
34
|
+
when '^='
|
35
|
+
actual_value(node).start_with?(expected_value)
|
36
|
+
when '$='
|
37
|
+
actual_value(node).end_with?(expected_value)
|
38
|
+
when '*='
|
39
|
+
actual_value(node).include?(expected_value)
|
40
|
+
when '>'
|
34
41
|
actual_value(node) > expected_value
|
35
|
-
when
|
42
|
+
when '>='
|
36
43
|
actual_value(node) >= expected_value
|
37
|
-
when
|
44
|
+
when '<'
|
38
45
|
actual_value(node) < expected_value
|
39
|
-
when
|
46
|
+
when '<='
|
40
47
|
actual_value(node) <= expected_value
|
41
|
-
when
|
42
|
-
expected_value.any? { |expected| expected.match?(node,
|
43
|
-
when
|
44
|
-
expected_value.all? { |expected| expected.match?(node,
|
45
|
-
when
|
48
|
+
when 'in'
|
49
|
+
expected_value.any? { |expected| expected.match?(node, '==') }
|
50
|
+
when 'not_in'
|
51
|
+
expected_value.all? { |expected| expected.match?(node, '!=') }
|
52
|
+
when 'includes'
|
46
53
|
actual_value(node).any? { |actual| actual == expected_value }
|
47
54
|
else
|
48
55
|
if expected_value.is_a?(::Array)
|
49
56
|
actual = actual_value(node)
|
50
57
|
actual.is_a?(::Array) && actual.size == expected_value.size &&
|
51
|
-
actual.zip(expected_value).all? { |actual_node, expected_node| expected_node.match?(actual_node,
|
58
|
+
actual.zip(expected_value).all? { |actual_node, expected_node| expected_node.match?(actual_node, '==') }
|
52
59
|
else
|
53
60
|
actual_value(node) == expected_value
|
54
61
|
end
|
@@ -6,11 +6,9 @@ module Synvert::Core::NodeQuery::Compiler
|
|
6
6
|
# Initialize a Expression.
|
7
7
|
# @param selector [Synvert::Core::NodeQuery::Compiler::Selector] the selector
|
8
8
|
# @param rest [Synvert::Core::NodeQuery::Compiler::Expression] the rest expression
|
9
|
-
|
10
|
-
def initialize(selector: nil, rest: nil, relationship: nil)
|
9
|
+
def initialize(selector: nil, rest: nil)
|
11
10
|
@selector = selector
|
12
11
|
@rest = rest
|
13
|
-
@relationship = relationship
|
14
12
|
end
|
15
13
|
|
16
14
|
# Check if the node matches the expression.
|
@@ -20,69 +18,41 @@ module Synvert::Core::NodeQuery::Compiler
|
|
20
18
|
!query_nodes(node).empty?
|
21
19
|
end
|
22
20
|
|
23
|
-
# Query nodes by the expression.
|
21
|
+
# Query nodes by the selector and the rest expression.
|
24
22
|
#
|
25
|
-
# * If relationship is nil, it will match in all recursive child nodes and return matching nodes.
|
26
|
-
# * If relationship is :decendant, it will match in all recursive child nodes.
|
27
|
-
# * If relationship is :child, it will match in direct child nodes.
|
28
|
-
# * If relationship is :next_sibling, it try to match next sibling node.
|
29
|
-
# * If relationship is :subsequent_sibling, it will match in all sibling nodes.
|
30
23
|
# @param node [Parser::AST::Node] node to match
|
31
24
|
# @param descendant_match [Boolean] whether to match in descendant node
|
32
25
|
# @return [Array<Parser::AST::Node>] matching nodes.
|
33
|
-
def query_nodes(node, descendant_match
|
34
|
-
matching_nodes =
|
35
|
-
if @
|
36
|
-
return matching_nodes
|
37
|
-
end
|
26
|
+
def query_nodes(node, descendant_match = true)
|
27
|
+
matching_nodes = find_nodes_by_selector(node, descendant_match)
|
28
|
+
return matching_nodes if @rest.nil?
|
38
29
|
|
39
|
-
|
40
|
-
case @relationship
|
41
|
-
when :descendant
|
42
|
-
nodes = []
|
43
|
-
matching_node.recursive_children { |child_node|
|
44
|
-
nodes += @rest.query_nodes(child_node, descendant_match: false)
|
45
|
-
}
|
46
|
-
nodes
|
47
|
-
when :child
|
48
|
-
matching_node.children.map { |child_node| @rest.query_nodes(child_node, descendant_match: false) }
|
49
|
-
.flatten
|
50
|
-
when :next_sibling
|
51
|
-
@rest.query_nodes(matching_node.siblings.first, descendant_match: false)
|
52
|
-
when :subsequent_sibling
|
53
|
-
matching_node.siblings.map { |sibling_node| @rest.query_nodes(sibling_node, descendant_match: false) }
|
54
|
-
.flatten
|
55
|
-
end
|
56
|
-
end.flatten
|
30
|
+
matching_nodes.flat_map { |matching_node| find_nodes_by_rest(matching_node, descendant_match) }
|
57
31
|
end
|
58
32
|
|
59
33
|
def to_s
|
60
|
-
return @selector.to_s unless @rest
|
61
|
-
|
62
34
|
result = []
|
63
|
-
result << @selector if @selector
|
64
|
-
|
65
|
-
|
66
|
-
when :subsequent_sibling then result << '~'
|
67
|
-
when :next_sibling then result << '+'
|
68
|
-
end
|
69
|
-
result << @rest
|
70
|
-
result.map(&:to_s).join(' ')
|
35
|
+
result << @selector.to_s if @selector
|
36
|
+
result << @rest.to_s if @rest
|
37
|
+
result.join(' ')
|
71
38
|
end
|
72
39
|
|
73
40
|
private
|
74
41
|
|
75
|
-
|
76
|
-
|
42
|
+
# Find nodes by @rest
|
43
|
+
# @param node [Parser::AST::Node] node to match
|
44
|
+
# @param descendant_match [Boolean] whether to match in descendant node
|
45
|
+
def find_nodes_by_rest(node, descendant_match = false)
|
46
|
+
@rest.query_nodes(node, descendant_match)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Find nodes with nil relationship.
|
50
|
+
# @param node [Parser::AST::Node] node to match
|
51
|
+
# @param descendant_match [Boolean] whether to match in descendant node
|
52
|
+
def find_nodes_by_selector(node, descendant_match = true)
|
53
|
+
return Array(node) if !@selector
|
77
54
|
|
78
|
-
|
79
|
-
nodes << node if @selector.match?(node)
|
80
|
-
if descendant_match
|
81
|
-
node.recursive_children do |child_node|
|
82
|
-
nodes << child_node if @selector.match?(child_node)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
@selector.filter(nodes)
|
55
|
+
@selector.query_nodes(node, descendant_match)
|
86
56
|
end
|
87
57
|
end
|
88
58
|
end
|
@@ -15,14 +15,14 @@ module Synvert::Core::NodeQuery::Compiler
|
|
15
15
|
# @param node [Parser::AST::Node] the node
|
16
16
|
# @param operator [Symbol] the operator
|
17
17
|
# @return [Boolean] true if the regexp value matches the node value, otherwise, false.
|
18
|
-
def match?(node, operator =
|
18
|
+
def match?(node, operator = '=~')
|
19
19
|
match =
|
20
20
|
if node.is_a?(::Parser::AST::Node)
|
21
21
|
@value.match(node.to_source)
|
22
22
|
else
|
23
23
|
@value.match(node.to_s)
|
24
24
|
end
|
25
|
-
operator ==
|
25
|
+
operator == '=~' ? match : !match
|
26
26
|
end
|
27
27
|
|
28
28
|
# Get valid operators.
|
@@ -4,50 +4,135 @@ module Synvert::Core::NodeQuery::Compiler
|
|
4
4
|
# Selector used to match nodes, it combines by node type and/or attribute list, plus index or has expression.
|
5
5
|
class Selector
|
6
6
|
# Initialize a Selector.
|
7
|
-
# @param
|
7
|
+
# @param goto_scope [String] goto scope
|
8
|
+
# @param relationship [Symbol] the relationship between the selectors, it can be descendant <code>nil</code>, child <code>></code>, next sibling <code>+</code> or subsequent sibing <code>~</code>.
|
9
|
+
# @param rest [Synvert::Core::NodeQuery::Compiler::Selector] the rest selector
|
10
|
+
# @param simple_selector [Synvert::Core::NodeQuery::Compiler::SimpleSelector] the simple selector
|
8
11
|
# @param attribute_list [Synvert::Core::NodeQuery::Compiler::AttributeList] the attribute list
|
9
12
|
# @param index [Integer] the index
|
10
13
|
# @param pseudo_class [String] the pseudo class, can be <code>has</code> or <code>not_has</code>
|
11
|
-
# @param
|
12
|
-
def initialize(
|
13
|
-
@
|
14
|
-
@
|
14
|
+
# @param pseudo_selector [Synvert::Core::NodeQuery::Compiler::Expression] the pseudo selector
|
15
|
+
def initialize(goto_scope: nil, relationship: nil, rest: nil, simple_selector: nil, index: nil, pseudo_class: nil, pseudo_selector: nil)
|
16
|
+
@goto_scope = goto_scope
|
17
|
+
@relationship = relationship
|
18
|
+
@rest = rest
|
19
|
+
@simple_selector = simple_selector
|
15
20
|
@index = index
|
16
21
|
@pseudo_class = pseudo_class
|
17
|
-
@
|
18
|
-
end
|
19
|
-
|
20
|
-
# Filter nodes by index.
|
21
|
-
def filter(nodes)
|
22
|
-
return nodes if @index.nil?
|
23
|
-
|
24
|
-
nodes[@index] ? [nodes[@index]] : []
|
22
|
+
@pseudo_selector = pseudo_selector
|
25
23
|
end
|
26
24
|
|
27
25
|
# Check if node matches the selector.
|
28
26
|
# @param node [Parser::AST::Node] the node
|
29
|
-
def match?(node
|
30
|
-
|
31
|
-
(!@
|
32
|
-
|
27
|
+
def match?(node)
|
28
|
+
node.is_a?(::Parser::AST::Node) &&
|
29
|
+
(!@simple_selector || @simple_selector.match?(node)) &&
|
30
|
+
match_pseudo_class?(node)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Query nodes by the selector.
|
34
|
+
#
|
35
|
+
# * If relationship is nil, it will match in all recursive child nodes and return matching nodes.
|
36
|
+
# * If relationship is decendant, it will match in all recursive child nodes.
|
37
|
+
# * If relationship is child, it will match in direct child nodes.
|
38
|
+
# * If relationship is next sibling, it try to match next sibling node.
|
39
|
+
# * If relationship is subsequent sibling, it will match in all sibling nodes.
|
40
|
+
# @param node [Parser::AST::Node] node to match
|
41
|
+
# @param descendant_match [Boolean] whether to match in descendant node
|
42
|
+
# @return [Array<Parser::AST::Node>] matching nodes.
|
43
|
+
def query_nodes(node, descendant_match = true)
|
44
|
+
return find_nodes_by_relationship(node) if @relationship
|
45
|
+
|
46
|
+
if node.is_a?(::Array)
|
47
|
+
return node.flat_map { |child_node| query_nodes(child_node, descendant_match) }
|
48
|
+
end
|
49
|
+
|
50
|
+
return find_nodes_by_goto_scope(node) if @goto_scope
|
51
|
+
|
52
|
+
nodes = []
|
53
|
+
nodes << node if match?(node)
|
54
|
+
if descendant_match && @simple_selector
|
55
|
+
node.recursive_children do |child_node|
|
56
|
+
nodes << child_node if match?(child_node)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
filter(nodes)
|
33
60
|
end
|
34
61
|
|
35
62
|
def to_s
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
63
|
+
result = []
|
64
|
+
result << "<#{@goto_scope}> " if @goto_scope
|
65
|
+
result << "#{@relationship} " if @relationship
|
66
|
+
result << @rest.to_s if @rest
|
67
|
+
result << @simple_selector.to_s if @simple_selector
|
68
|
+
result << ":#{@pseudo_class}(#{@pseudo_selector})" if @pseudo_class
|
69
|
+
if @index
|
70
|
+
result <<
|
71
|
+
case @index
|
72
|
+
when 0
|
73
|
+
':first-child'
|
74
|
+
when -1
|
75
|
+
':last-child'
|
76
|
+
when (1..)
|
77
|
+
":nth-child(#{@index + 1})"
|
78
|
+
else # ...-1
|
79
|
+
":nth-last-child(#{-@index})"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
result.join('')
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# Find nodes by @goto_scope
|
88
|
+
# @param node [Parser::AST::Node] node to match
|
89
|
+
def find_nodes_by_goto_scope(node)
|
90
|
+
@goto_scope.split('.').each { |scope| node = node.send(scope) }
|
91
|
+
@rest.query_nodes(node, false)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Find ndoes by @relationship
|
95
|
+
# @param node [Parser::AST::Node] node to match
|
96
|
+
def find_nodes_by_relationship(node)
|
97
|
+
nodes = []
|
98
|
+
case @relationship
|
99
|
+
when '>'
|
100
|
+
if node.is_a?(::Array)
|
101
|
+
node.each do |child_node|
|
102
|
+
nodes << child_node if @rest.match?(child_node)
|
103
|
+
end
|
104
|
+
else
|
105
|
+
node.children.each do |child_node|
|
106
|
+
nodes << child_node if @rest.match?(child_node)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
when '+'
|
110
|
+
next_sibling = node.siblings.first
|
111
|
+
nodes << next_sibling if @rest.match?(next_sibling)
|
112
|
+
when '~'
|
113
|
+
node.siblings.each do |sibling_node|
|
114
|
+
nodes << sibling_node if @rest.match?(sibling_node)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
return filter(nodes)
|
118
|
+
end
|
119
|
+
|
120
|
+
def match_pseudo_class?(node)
|
121
|
+
case @pseudo_class
|
122
|
+
when 'has'
|
123
|
+
!@pseudo_selector.query_nodes(node).empty?
|
124
|
+
when 'not_has'
|
125
|
+
@pseudo_selector.query_nodes(node).empty?
|
126
|
+
else
|
127
|
+
true
|
50
128
|
end
|
51
129
|
end
|
130
|
+
|
131
|
+
# Filter nodes by index.
|
132
|
+
def filter(nodes)
|
133
|
+
return nodes if @index.nil?
|
134
|
+
|
135
|
+
nodes[@index] ? [nodes[@index]] : []
|
136
|
+
end
|
52
137
|
end
|
53
138
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Synvert::Core::NodeQuery::Compiler
|
4
|
+
# SimpleSelector used to match nodes, it combines by node type and/or attribute list.
|
5
|
+
class SimpleSelector
|
6
|
+
# Initialize a SimpleSelector.
|
7
|
+
# @param node_type [String] the node type
|
8
|
+
# @param attribute_list [Synvert::Core::NodeQuery::Compiler::AttributeList] the attribute list
|
9
|
+
def initialize(node_type:, attribute_list: nil)
|
10
|
+
@node_type = node_type
|
11
|
+
@attribute_list = attribute_list
|
12
|
+
end
|
13
|
+
|
14
|
+
# Check if node matches the selector.
|
15
|
+
# @param node [Parser::AST::Node] the node
|
16
|
+
def match?(node, _operator = '==')
|
17
|
+
return false unless node
|
18
|
+
|
19
|
+
@node_type.to_sym == node.type && (!@attribute_list || @attribute_list.match?(node))
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
result = []
|
24
|
+
result << ".#{@node_type}" if @node_type
|
25
|
+
result << @attribute_list.to_s if @attribute_list
|
26
|
+
result.join('')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -8,6 +8,7 @@ module Synvert::Core::NodeQuery::Compiler
|
|
8
8
|
|
9
9
|
autoload :Expression, 'synvert/core/node_query/compiler/expression'
|
10
10
|
autoload :Selector, 'synvert/core/node_query/compiler/selector'
|
11
|
+
autoload :SimpleSelector, 'synvert/core/node_query/compiler/simple_selector'
|
11
12
|
autoload :AttributeList, 'synvert/core/node_query/compiler/attribute_list'
|
12
13
|
autoload :Attribute, 'synvert/core/node_query/compiler/attribute'
|
13
14
|
|