synvert-core 1.1.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|