synvert-core 1.2.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/Gemfile +9 -0
- data/lib/synvert/core/node_ext.rb +14 -2
- 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 +8 -76
- 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 +102 -20
- 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 +19 -16
- data/lib/synvert/core/node_query/lexer.rex.rb +34 -27
- data/lib/synvert/core/node_query/parser.racc.rb +118 -287
- data/lib/synvert/core/node_query/parser.y +17 -36
- data/lib/synvert/core/node_query.rb +7 -7
- data/lib/synvert/core/rewriter/action/delete_action.rb +4 -2
- data/lib/synvert/core/rewriter/action/remove_action.rb +5 -2
- data/lib/synvert/core/rewriter/instance.rb +8 -4
- 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 +48 -48
- data/spec/synvert/core/node_query/parser_spec.rb +139 -107
- data/spec/synvert/core/rewriter/instance_spec.rb +12 -7
- 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: 51aff3b9c1c43ab2e6b3c093b65a42cb97536b900cf0aa8d0081e7e8716346f0
|
4
|
+
data.tar.gz: 6902ad17b6fa2d1ab2a5d5294f3bb1f29903a12c7ad8dd15cc7dae56173a440a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 74cc5bb2cf144fba8bbd8ad647971efeba9473138d1cc5b416b45aa940c22f8929cefdd32b5e692c0dc1a9c9786d49dd8b441f1949a58256cec015a9697f2a43
|
7
|
+
data.tar.gz: c08abee0bf9fd0cba7cd5248f9020c4c30dadc25c06c5b358a122ccb09e7171878eae85cd498a0fb788aa2987fcf0302916c49cca0f9eaabc77b55e3a43d6a3a
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 1.3.1 (2022-05-14)
|
4
|
+
|
5
|
+
* Add `add_comma` option to remove extra comma
|
6
|
+
|
7
|
+
## 1.3.0 (2022-05-12)
|
8
|
+
|
9
|
+
* Support `*=`, `^=` and `$=` operators
|
10
|
+
* Simplify RELATIONSHIP parser
|
11
|
+
* Rewrite compiler, let the selector to query nodes
|
12
|
+
|
13
|
+
## 1.2.1 (2022-05-01)
|
14
|
+
|
15
|
+
* Selector always after a node type in NQL
|
16
|
+
* Define `pairs` method for `hash` ndoe
|
17
|
+
|
3
18
|
## 1.2.0 (2022-04-29)
|
4
19
|
|
5
20
|
* Remove comma in NQL array value
|
data/Gemfile
CHANGED
@@ -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
|
@@ -5,14 +5,10 @@ module Synvert::Core::NodeQuery::Compiler
|
|
5
5
|
class Expression
|
6
6
|
# Initialize a Expression.
|
7
7
|
# @param selector [Synvert::Core::NodeQuery::Compiler::Selector] the selector
|
8
|
-
# @param goto_scope [String] goto scope
|
9
8
|
# @param rest [Synvert::Core::NodeQuery::Compiler::Expression] the rest expression
|
10
|
-
|
11
|
-
def initialize(selector: nil, goto_scope: nil, rest: nil, relationship: nil)
|
9
|
+
def initialize(selector: nil, rest: nil)
|
12
10
|
@selector = selector
|
13
|
-
@goto_scope = goto_scope
|
14
11
|
@rest = rest
|
15
|
-
@relationship = relationship
|
16
12
|
end
|
17
13
|
|
18
14
|
# Check if the node matches the expression.
|
@@ -22,78 +18,27 @@ module Synvert::Core::NodeQuery::Compiler
|
|
22
18
|
!query_nodes(node).empty?
|
23
19
|
end
|
24
20
|
|
25
|
-
# Query nodes by the expression.
|
21
|
+
# Query nodes by the selector and the rest expression.
|
26
22
|
#
|
27
|
-
# * If relationship is nil, it will match in all recursive child nodes and return matching nodes.
|
28
|
-
# * If relationship is :decendant, it will match in all recursive child nodes.
|
29
|
-
# * If relationship is :child, it will match in direct child nodes.
|
30
|
-
# * If relationship is :next_sibling, it try to match next sibling node.
|
31
|
-
# * If relationship is :subsequent_sibling, it will match in all sibling nodes.
|
32
23
|
# @param node [Parser::AST::Node] node to match
|
33
24
|
# @param descendant_match [Boolean] whether to match in descendant node
|
34
25
|
# @return [Array<Parser::AST::Node>] matching nodes.
|
35
26
|
def query_nodes(node, descendant_match = true)
|
36
|
-
|
37
|
-
|
38
|
-
return find_nodes_by_relationship(node) if @relationship
|
39
|
-
|
40
|
-
matching_nodes = find_nodes_without_relationship(node, descendant_match)
|
27
|
+
matching_nodes = find_nodes_by_selector(node, descendant_match)
|
41
28
|
return matching_nodes if @rest.nil?
|
42
29
|
|
43
|
-
matching_nodes.
|
44
|
-
.flatten
|
30
|
+
matching_nodes.flat_map { |matching_node| find_nodes_by_rest(matching_node, descendant_match) }
|
45
31
|
end
|
46
32
|
|
47
33
|
def to_s
|
48
|
-
return @selector.to_s unless @rest
|
49
|
-
|
50
34
|
result = []
|
51
35
|
result << @selector.to_s if @selector
|
52
|
-
result <<
|
53
|
-
case @relationship
|
54
|
-
when :child then result << "> #{@rest}"
|
55
|
-
when :subsequent_sibling then result << "~ #{@rest}"
|
56
|
-
when :next_sibling then result << "+ #{@rest}"
|
57
|
-
when :has then result << ":has(#{@rest})"
|
58
|
-
when :not_has then result << ":not_has(#{@rest})"
|
59
|
-
else result << @rest.to_s
|
60
|
-
end
|
36
|
+
result << @rest.to_s if @rest
|
61
37
|
result.join(' ')
|
62
38
|
end
|
63
39
|
|
64
40
|
private
|
65
41
|
|
66
|
-
# Find nodes by @goto_scope
|
67
|
-
# @param node [Parser::AST::Node] node to match
|
68
|
-
def find_nodes_by_goto_scope(node)
|
69
|
-
@goto_scope.split('.').each { |scope| node = node.send(scope) }
|
70
|
-
@rest.query_nodes(node, false)
|
71
|
-
end
|
72
|
-
|
73
|
-
# Find ndoes by @relationship
|
74
|
-
# @param node [Parser::AST::Node] node to match
|
75
|
-
def find_nodes_by_relationship(node)
|
76
|
-
case @relationship
|
77
|
-
when :child
|
78
|
-
if node.is_a?(::Array)
|
79
|
-
return node.map { |each_node| find_nodes_by_rest(each_node) }
|
80
|
-
.flatten
|
81
|
-
else
|
82
|
-
return node.children.map { |each_node| find_nodes_by_rest(each_node) }
|
83
|
-
.flatten
|
84
|
-
end
|
85
|
-
when :next_sibling
|
86
|
-
return find_nodes_by_rest(node.siblings.first)
|
87
|
-
when :subsequent_sibling
|
88
|
-
return node.siblings.map { |each_node| find_nodes_by_rest(each_node) }
|
89
|
-
.flatten
|
90
|
-
when :has
|
91
|
-
return @rest.match?(node) ? [node] : []
|
92
|
-
when :not_has
|
93
|
-
return !@rest.match?(node) ? [node] : []
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
42
|
# Find nodes by @rest
|
98
43
|
# @param node [Parser::AST::Node] node to match
|
99
44
|
# @param descendant_match [Boolean] whether to match in descendant node
|
@@ -104,23 +49,10 @@ module Synvert::Core::NodeQuery::Compiler
|
|
104
49
|
# Find nodes with nil relationship.
|
105
50
|
# @param node [Parser::AST::Node] node to match
|
106
51
|
# @param descendant_match [Boolean] whether to match in descendant node
|
107
|
-
def
|
108
|
-
|
109
|
-
return node.map { |each_node|
|
110
|
-
find_nodes_without_relationship(each_node, descendant_match)
|
111
|
-
}.flatten
|
112
|
-
end
|
113
|
-
|
114
|
-
return [node] unless @selector
|
52
|
+
def find_nodes_by_selector(node, descendant_match = true)
|
53
|
+
return Array(node) if !@selector
|
115
54
|
|
116
|
-
|
117
|
-
nodes << node if @selector.match?(node)
|
118
|
-
if descendant_match
|
119
|
-
node.recursive_children do |child_node|
|
120
|
-
nodes << child_node if @selector.match?(child_node)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
@selector.filter(nodes)
|
55
|
+
@selector.query_nodes(node, descendant_match)
|
124
56
|
end
|
125
57
|
end
|
126
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,39 +4,68 @@ 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
63
|
result = []
|
37
|
-
result << "
|
38
|
-
result << @
|
39
|
-
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
|
40
69
|
if @index
|
41
70
|
result <<
|
42
71
|
case @index
|
@@ -52,5 +81,58 @@ module Synvert::Core::NodeQuery::Compiler
|
|
52
81
|
end
|
53
82
|
result.join('')
|
54
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
|
128
|
+
end
|
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
|
55
137
|
end
|
56
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
|
|
@@ -22,8 +22,8 @@ macros
|
|
22
22
|
REGEXP /\/(#{REGEXP_BODY})(?<!\\)\/([imxo]*)/
|
23
23
|
SYMBOL /:[\w!\?<>=]+/
|
24
24
|
TRUE /true/
|
25
|
-
SINGLE_QUOTE_STRING /'
|
26
|
-
DOUBLE_QUOTE_STRING /"
|
25
|
+
SINGLE_QUOTE_STRING /'.*?'/
|
26
|
+
DOUBLE_QUOTE_STRING /".*?"/
|
27
27
|
|
28
28
|
rules
|
29
29
|
|
@@ -36,9 +36,9 @@ rules
|
|
36
36
|
/:has/ { [:tPSEUDO_CLASS, text[1..-1]] }
|
37
37
|
/:not_has/ { [:tPSEUDO_CLASS, text[1..-1]] }
|
38
38
|
/#{NODE_TYPE}/ { [:tNODE_TYPE, text[1..]] }
|
39
|
-
/>/ { [:
|
40
|
-
/~/ { [:
|
41
|
-
/\+/ { [:
|
39
|
+
/>/ { [:tRELATIONSHIP, text] }
|
40
|
+
/~/ { [:tRELATIONSHIP, text] }
|
41
|
+
/\+/ { [:tRELATIONSHIP, text] }
|
42
42
|
/#{OPEN_SELECTOR}/ { [:tOPEN_SELECTOR, text] }
|
43
43
|
/#{CLOSE_SELECTOR}/ { [:tCLOSE_SELECTOR, text] }
|
44
44
|
/#{OPEN_GOTO_SCOPE}/ { @state = :GOTO_SCOPE; [:tOPEN_GOTO_SCOPE, text] }
|
@@ -47,17 +47,20 @@ rules
|
|
47
47
|
:GOTO_SCOPE /#{IDENTIFIER}/ { [:tIDENTIFIER, text] }
|
48
48
|
:GOTO_SCOPE /#{CLOSE_GOTO_SCOPE}/ { @state = nil; [:tCLOSE_GOTO_SCOPE, text] }
|
49
49
|
:KEY /\s+/
|
50
|
-
:KEY
|
51
|
-
:KEY
|
52
|
-
:KEY
|
53
|
-
:KEY
|
54
|
-
:KEY
|
55
|
-
:KEY
|
56
|
-
:KEY
|
57
|
-
:KEY
|
58
|
-
:KEY
|
59
|
-
:KEY
|
60
|
-
:KEY
|
50
|
+
:KEY /\^=/ { @state = :VALUE; [:tOPERATOR, '^='] }
|
51
|
+
:KEY /\$=/ { @state = :VALUE; [:tOPERATOR, '$='] }
|
52
|
+
:KEY /\*=/ { @state = :VALUE; [:tOPERATOR, '*='] }
|
53
|
+
:KEY /!=/ { @state = :VALUE; [:tOPERATOR, '!='] }
|
54
|
+
:KEY /=~/ { @state = :VALUE; [:tOPERATOR, '=~'] }
|
55
|
+
:KEY /!~/ { @state = :VALUE; [:tOPERATOR, '!~'] }
|
56
|
+
:KEY />=/ { @state = :VALUE; [:tOPERATOR, '>='] }
|
57
|
+
:KEY /<=/ { @state = :VALUE; [:tOPERATOR, '<='] }
|
58
|
+
:KEY />/ { @state = :VALUE; [:tOPERATOR, '>'] }
|
59
|
+
:KEY /</ { @state = :VALUE; [:tOPERATOR, '<'] }
|
60
|
+
:KEY /=/ { @state = :VALUE; [:tOPERATOR, '=='] }
|
61
|
+
:KEY /includes/i { @state = :VALUE; [:tOPERATOR, 'includes'] }
|
62
|
+
:KEY /not in/i { @state = :VALUE; [:tOPERATOR, 'not_in'] }
|
63
|
+
:KEY /in/i { @state = :VALUE; [:tOPERATOR, 'in'] }
|
61
64
|
:KEY /#{IDENTIFIER}/ { [:tKEY, text] }
|
62
65
|
:VALUE /\s+/
|
63
66
|
:VALUE /\[\]=/ { [:tIDENTIFIER_VALUE, text] }
|