synvert-core 1.1.0 → 1.2.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 +17 -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/expression.rb +69 -31
- data/lib/synvert/core/node_query/compiler/selector.rb +17 -14
- data/lib/synvert/core/node_query/compiler/string.rb +0 -11
- data/lib/synvert/core/node_query/lexer.rex +12 -1
- data/lib/synvert/core/node_query/lexer.rex.rb +28 -2
- data/lib/synvert/core/node_query/parser.racc.rb +219 -253
- data/lib/synvert/core/node_query/parser.y +9 -14
- 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 +32 -24
- data/spec/synvert/core/node_query/lexer_spec.rb +56 -4
- data/spec/synvert/core/node_query/parser_spec.rb +90 -25
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c530d34ca0e2db5d9a295cb0cb48c375d13760f09290624805d742328a49103
|
4
|
+
data.tar.gz: 87cc4ef7ab9116bac2cbed78b9f71ab8d4ffa30281554d10319527a9aab94573
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b864e1e350d3467fe12d2c5e26a5c5a0b685752e21802e3773edfbbb8fca8f7dec60fb545475a630a3f00c09359c66859b228cf7cb3d19aa6bf044df4848408
|
7
|
+
data.tar.gz: c8c5641b85080f03307fed835c05132488d140bb19b694c2b5b0958cb3d6377785ef1447c8b7fec59e7529f2ec610f6c78d72357b60f5fe4285d43d5071750fb
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 1.2.1 (2022-05-01)
|
4
|
+
|
5
|
+
* Selector always after a node type in NQL
|
6
|
+
* Define `pairs` method for `hash` ndoe
|
7
|
+
|
8
|
+
## 1.2.0 (2022-04-29)
|
9
|
+
|
10
|
+
* Remove comma in NQL array value
|
11
|
+
* Parse pseduo class without selector in NQL
|
12
|
+
* Parse multiple goto scope in NQL
|
13
|
+
* Parse `nil?` in NQL
|
14
|
+
|
15
|
+
## 1.1.1 (2022-04-27)
|
16
|
+
|
17
|
+
* Parse empty string properly in node query language
|
18
|
+
* Parse `[]` and `[]=` properly in node query language
|
19
|
+
|
3
20
|
## 1.1.0 (2022-04-26)
|
4
21
|
|
5
22
|
* Dynamic define Node methods by `TYPE_CHILDREN` const
|
@@ -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
|
@@ -5,10 +5,12 @@ 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
|
8
9
|
# @param rest [Synvert::Core::NodeQuery::Compiler::Expression] the rest expression
|
9
10
|
# @param relationship [Symbol] the relationship between the selector and rest expression, it can be <code>:descendant</code>, <code>:child</code>, <code>:next_sibling</code>, <code>:subsequent_sibling</code> or <code>nil</code>.
|
10
|
-
def initialize(selector: nil, rest: nil, relationship: nil)
|
11
|
+
def initialize(selector: nil, goto_scope: nil, rest: nil, relationship: nil)
|
11
12
|
@selector = selector
|
13
|
+
@goto_scope = goto_scope
|
12
14
|
@rest = rest
|
13
15
|
@relationship = relationship
|
14
16
|
end
|
@@ -30,49 +32,85 @@ module Synvert::Core::NodeQuery::Compiler
|
|
30
32
|
# @param node [Parser::AST::Node] node to match
|
31
33
|
# @param descendant_match [Boolean] whether to match in descendant node
|
32
34
|
# @return [Array<Parser::AST::Node>] matching nodes.
|
33
|
-
def query_nodes(node, descendant_match
|
34
|
-
|
35
|
-
if @relationship.nil?
|
36
|
-
return matching_nodes
|
37
|
-
end
|
35
|
+
def query_nodes(node, descendant_match = true)
|
36
|
+
return find_nodes_by_goto_scope(node) if @goto_scope
|
38
37
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
38
|
+
return find_nodes_by_relationship(node) if @relationship
|
39
|
+
|
40
|
+
matching_nodes = find_nodes_without_relationship(node, descendant_match)
|
41
|
+
return matching_nodes if @rest.nil?
|
42
|
+
|
43
|
+
matching_nodes.map { |matching_node| find_nodes_by_rest(matching_node, descendant_match) }
|
44
|
+
.flatten
|
57
45
|
end
|
58
46
|
|
59
47
|
def to_s
|
60
48
|
return @selector.to_s unless @rest
|
61
49
|
|
62
50
|
result = []
|
63
|
-
result << @selector if @selector
|
51
|
+
result << @selector.to_s if @selector
|
52
|
+
result << "<#{@goto_scope}>" if @goto_scope
|
64
53
|
case @relationship
|
65
|
-
when :child then result <<
|
66
|
-
when :subsequent_sibling then result <<
|
67
|
-
when :next_sibling then result <<
|
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
|
68
60
|
end
|
69
|
-
result
|
70
|
-
result.map(&:to_s).join(' ')
|
61
|
+
result.join(' ')
|
71
62
|
end
|
72
63
|
|
73
64
|
private
|
74
65
|
|
75
|
-
|
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
|
+
# Find nodes by @rest
|
98
|
+
# @param node [Parser::AST::Node] node to match
|
99
|
+
# @param descendant_match [Boolean] whether to match in descendant node
|
100
|
+
def find_nodes_by_rest(node, descendant_match = false)
|
101
|
+
@rest.query_nodes(node, descendant_match)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Find nodes with nil relationship.
|
105
|
+
# @param node [Parser::AST::Node] node to match
|
106
|
+
# @param descendant_match [Boolean] whether to match in descendant node
|
107
|
+
def find_nodes_without_relationship(node, descendant_match = true)
|
108
|
+
if node.is_a?(::Array)
|
109
|
+
return node.map { |each_node|
|
110
|
+
find_nodes_without_relationship(each_node, descendant_match)
|
111
|
+
}.flatten
|
112
|
+
end
|
113
|
+
|
76
114
|
return [node] unless @selector
|
77
115
|
|
78
116
|
nodes = []
|
@@ -33,21 +33,24 @@ module Synvert::Core::NodeQuery::Compiler
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def to_s
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
36
|
+
result = []
|
37
|
+
result << ".#{@node_type}" if @node_type
|
38
|
+
result << @attribute_list.to_s if @attribute_list
|
39
|
+
result << ":#{@pseudo_class}(#{@pseudo_expression})" if @pseudo_class
|
40
|
+
if @index
|
41
|
+
result <<
|
42
|
+
case @index
|
43
|
+
when 0
|
44
|
+
':first-child'
|
45
|
+
when -1
|
46
|
+
':last-child'
|
47
|
+
when (1..)
|
48
|
+
":nth-child(#{@index + 1})"
|
49
|
+
else # ...-1
|
50
|
+
":nth-last-child(#{-@index})"
|
51
|
+
end
|
50
52
|
end
|
53
|
+
result.join('')
|
51
54
|
end
|
52
55
|
end
|
53
56
|
end
|
@@ -16,17 +16,6 @@ module Synvert::Core::NodeQuery::Compiler
|
|
16
16
|
SIMPLE_VALID_OPERATORS
|
17
17
|
end
|
18
18
|
|
19
|
-
# Get the actual value of a node.
|
20
|
-
# @param node [Parser::AST::Node] the node
|
21
|
-
# @return [String] if node is a Parser::AST::Node, return the node source code, otherwise, return the string value.
|
22
|
-
def actual_value(node)
|
23
|
-
if node.is_a?(::Parser::AST::Node)
|
24
|
-
node.to_source
|
25
|
-
else
|
26
|
-
node.to_s
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
19
|
def to_s
|
31
20
|
"\"#{@value}\""
|
32
21
|
end
|
@@ -7,6 +7,8 @@ macros
|
|
7
7
|
CLOSE_ARRAY /\)/
|
8
8
|
OPEN_SELECTOR /\(/
|
9
9
|
CLOSE_SELECTOR /\)/
|
10
|
+
OPEN_GOTO_SCOPE /</
|
11
|
+
CLOSE_GOTO_SCOPE />/
|
10
12
|
OPEN_DYNAMIC_ATTRIBUTE /{{/
|
11
13
|
CLOSE_DYNAMIC_ATTRIBUTE /}}/
|
12
14
|
NODE_TYPE /\.[a-z]+/
|
@@ -39,7 +41,11 @@ rules
|
|
39
41
|
/\+/ { [:tNEXT_SIBLING, text] }
|
40
42
|
/#{OPEN_SELECTOR}/ { [:tOPEN_SELECTOR, text] }
|
41
43
|
/#{CLOSE_SELECTOR}/ { [:tCLOSE_SELECTOR, text] }
|
44
|
+
/#{OPEN_GOTO_SCOPE}/ { @state = :GOTO_SCOPE; [:tOPEN_GOTO_SCOPE, text] }
|
42
45
|
/#{OPEN_ATTRIBUTE}/ { @nested_count += 1; @state = :KEY; [:tOPEN_ATTRIBUTE, text] }
|
46
|
+
:GOTO_SCOPE /\s+/
|
47
|
+
:GOTO_SCOPE /#{IDENTIFIER}/ { [:tIDENTIFIER, text] }
|
48
|
+
:GOTO_SCOPE /#{CLOSE_GOTO_SCOPE}/ { @state = nil; [:tCLOSE_GOTO_SCOPE, text] }
|
43
49
|
:KEY /\s+/
|
44
50
|
:KEY /!=/ { @state = :VALUE; [:tNOT_EQUAL, text] }
|
45
51
|
:KEY /=~/ { @state = :VALUE; [:tMATCH, text] }
|
@@ -54,9 +60,14 @@ rules
|
|
54
60
|
:KEY /in/i { @state = :VALUE; [:tIN, text] }
|
55
61
|
:KEY /#{IDENTIFIER}/ { [:tKEY, text] }
|
56
62
|
:VALUE /\s+/
|
63
|
+
:VALUE /\[\]=/ { [:tIDENTIFIER_VALUE, text] }
|
64
|
+
:VALUE /\[\]/ { [:tIDENTIFIER_VALUE, text] }
|
65
|
+
:VALUE /:\[\]=/ { [:tSYMBOL, text[1..-1].to_sym] }
|
66
|
+
:VALUE /:\[\]/ { [:tSYMBOL, text[1..-1].to_sym] }
|
57
67
|
:VALUE /#{OPEN_DYNAMIC_ATTRIBUTE}/ { @state = :DYNAMIC_ATTRIBUTE; [:tOPEN_DYNAMIC_ATTRIBUTE, text] }
|
58
68
|
:VALUE /#{OPEN_ARRAY}/ { @state = :ARRAY_VALUE; [:tOPEN_ARRAY, text] }
|
59
69
|
:VALUE /#{CLOSE_ATTRIBUTE}/ { @nested_count -= 1; @state = @nested_count == 0 ? nil : :VALUE; [:tCLOSE_ATTRIBUTE, text] }
|
70
|
+
:VALUE /#{NIL}\?/ { [:tIDENTIFIER_VALUE, text] }
|
60
71
|
:VALUE /#{NIL}/ { [:tNIL, nil] }
|
61
72
|
:VALUE /#{TRUE}/ { [:tBOOLEAN, true] }
|
62
73
|
:VALUE /#{FALSE}/ { [:tBOOLEAN, false] }
|
@@ -72,8 +83,8 @@ rules
|
|
72
83
|
:DYNAMIC_ATTRIBUTE /#{CLOSE_DYNAMIC_ATTRIBUTE}/ { @state = :VALUE; [:tCLOSE_DYNAMIC_ATTRIBUTE, text] }
|
73
84
|
:DYNAMIC_ATTRIBUTE /#{IDENTIFIER}/ { [:tDYNAMIC_ATTRIBUTE, text] }
|
74
85
|
:ARRAY_VALUE /\s+/
|
75
|
-
:ARRAY_VALUE /,/ { [:tCOMMA, text] }
|
76
86
|
:ARRAY_VALUE /#{CLOSE_ARRAY}/ { @state = :VALUE; [:tCLOSE_ARRAY, text] }
|
87
|
+
:ARRAY_VALUE /#{NIL}\?/ { [:tIDENTIFIER_VALUE, text] }
|
77
88
|
:ARRAY_VALUE /#{NIL}/ { [:tNIL, nil] }
|
78
89
|
:ARRAY_VALUE /#{TRUE}/ { [:tBOOLEAN, true] }
|
79
90
|
:ARRAY_VALUE /#{FALSE}/ { [:tBOOLEAN, false] }
|
@@ -20,6 +20,8 @@ class Synvert::Core::NodeQuery::Lexer
|
|
20
20
|
CLOSE_ARRAY = /\)/
|
21
21
|
OPEN_SELECTOR = /\(/
|
22
22
|
CLOSE_SELECTOR = /\)/
|
23
|
+
OPEN_GOTO_SCOPE = /</
|
24
|
+
CLOSE_GOTO_SCOPE = />/
|
23
25
|
OPEN_DYNAMIC_ATTRIBUTE = /{{/
|
24
26
|
CLOSE_DYNAMIC_ATTRIBUTE = /}}/
|
25
27
|
NODE_TYPE = /\.[a-z]+/
|
@@ -149,12 +151,26 @@ class Synvert::Core::NodeQuery::Lexer
|
|
149
151
|
action { [:tOPEN_SELECTOR, text] }
|
150
152
|
when text = ss.scan(/#{CLOSE_SELECTOR}/) then
|
151
153
|
action { [:tCLOSE_SELECTOR, text] }
|
154
|
+
when text = ss.scan(/#{OPEN_GOTO_SCOPE}/) then
|
155
|
+
action { @state = :GOTO_SCOPE; [:tOPEN_GOTO_SCOPE, text] }
|
152
156
|
when text = ss.scan(/#{OPEN_ATTRIBUTE}/) then
|
153
157
|
action { @nested_count += 1; @state = :KEY; [:tOPEN_ATTRIBUTE, text] }
|
154
158
|
else
|
155
159
|
text = ss.string[ss.pos .. -1]
|
156
160
|
raise ScanError, "can not match (#{state.inspect}) at #{location}: '#{text}'"
|
157
161
|
end
|
162
|
+
when :GOTO_SCOPE then
|
163
|
+
case
|
164
|
+
when ss.skip(/\s+/) then
|
165
|
+
# do nothing
|
166
|
+
when text = ss.scan(/#{IDENTIFIER}/) then
|
167
|
+
action { [:tIDENTIFIER, text] }
|
168
|
+
when text = ss.scan(/#{CLOSE_GOTO_SCOPE}/) then
|
169
|
+
action { @state = nil; [:tCLOSE_GOTO_SCOPE, text] }
|
170
|
+
else
|
171
|
+
text = ss.string[ss.pos .. -1]
|
172
|
+
raise ScanError, "can not match (#{state.inspect}) at #{location}: '#{text}'"
|
173
|
+
end
|
158
174
|
when :KEY then
|
159
175
|
case
|
160
176
|
when ss.skip(/\s+/) then
|
@@ -191,12 +207,22 @@ class Synvert::Core::NodeQuery::Lexer
|
|
191
207
|
case
|
192
208
|
when ss.skip(/\s+/) then
|
193
209
|
# do nothing
|
210
|
+
when text = ss.scan(/\[\]=/) then
|
211
|
+
action { [:tIDENTIFIER_VALUE, text] }
|
212
|
+
when text = ss.scan(/\[\]/) then
|
213
|
+
action { [:tIDENTIFIER_VALUE, text] }
|
214
|
+
when text = ss.scan(/:\[\]=/) then
|
215
|
+
action { [:tSYMBOL, text[1..-1].to_sym] }
|
216
|
+
when text = ss.scan(/:\[\]/) then
|
217
|
+
action { [:tSYMBOL, text[1..-1].to_sym] }
|
194
218
|
when text = ss.scan(/#{OPEN_DYNAMIC_ATTRIBUTE}/) then
|
195
219
|
action { @state = :DYNAMIC_ATTRIBUTE; [:tOPEN_DYNAMIC_ATTRIBUTE, text] }
|
196
220
|
when text = ss.scan(/#{OPEN_ARRAY}/) then
|
197
221
|
action { @state = :ARRAY_VALUE; [:tOPEN_ARRAY, text] }
|
198
222
|
when text = ss.scan(/#{CLOSE_ATTRIBUTE}/) then
|
199
223
|
action { @nested_count -= 1; @state = @nested_count == 0 ? nil : :VALUE; [:tCLOSE_ATTRIBUTE, text] }
|
224
|
+
when text = ss.scan(/#{NIL}\?/) then
|
225
|
+
action { [:tIDENTIFIER_VALUE, text] }
|
200
226
|
when ss.skip(/#{NIL}/) then
|
201
227
|
action { [:tNIL, nil] }
|
202
228
|
when ss.skip(/#{TRUE}/) then
|
@@ -239,10 +265,10 @@ class Synvert::Core::NodeQuery::Lexer
|
|
239
265
|
case
|
240
266
|
when ss.skip(/\s+/) then
|
241
267
|
# do nothing
|
242
|
-
when text = ss.scan(/,/) then
|
243
|
-
action { [:tCOMMA, text] }
|
244
268
|
when text = ss.scan(/#{CLOSE_ARRAY}/) then
|
245
269
|
action { @state = :VALUE; [:tCLOSE_ARRAY, text] }
|
270
|
+
when text = ss.scan(/#{NIL}\?/) then
|
271
|
+
action { [:tIDENTIFIER_VALUE, text] }
|
246
272
|
when ss.skip(/#{NIL}/) then
|
247
273
|
action { [:tNIL, nil] }
|
248
274
|
when ss.skip(/#{TRUE}/) then
|