synvert-core 1.1.0 → 1.2.1
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 +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
|