synvert-core 1.2.1 → 1.4.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 +16 -0
- data/Gemfile +9 -0
- data/lib/synvert/core/node_query/compiler/attribute.rb +6 -18
- data/lib/synvert/core/node_query/compiler/basic_selector.rb +28 -0
- data/lib/synvert/core/node_query/compiler/comparable.rb +26 -19
- data/lib/synvert/core/node_query/compiler/expression.rb +8 -93
- 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 +91 -34
- 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 +20 -26
- data/lib/synvert/core/node_query/lexer.rex.rb +35 -51
- data/lib/synvert/core/node_query/parser.racc.rb +115 -293
- data/lib/synvert/core/node_query/parser.y +16 -35
- data/lib/synvert/core/node_query.rb +3 -4
- 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_query/lexer_spec.rb +46 -106
- data/spec/synvert/core/node_query/parser_spec.rb +116 -129
- data/spec/synvert/core/rewriter/instance_spec.rb +12 -7
- data/spec/synvert/core/rewriter/scope/query_scope_spec.rb +9 -2
- 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: 741ca9c2f6f29e5bbafbc5b63d94b3cd9a56aa20e8a00f3f70830b73b1fb697e
|
4
|
+
data.tar.gz: a8bbf6e0c48a4af42578abb20807f3d86359fc069dc85ff90e12cf011f076998
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7fcadf632c863b1f3ba410b65f63c741b594255567f337ebc86c51ae45e681484653b45eec01fc5c3e3a4ea170a6bd38e899bdf22f65b2e980d58cac8c11cfcd
|
7
|
+
data.tar.gz: 80ebe8f07ef9679c6921233d52b46979a6bfb895e4b63b974672dec193ef52ce08c40454d2a92fd509f12b8f3567065bb7d2dbee33af7bca37a96246e6373381
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 1.4.0 (2022-05-21)
|
4
|
+
|
5
|
+
* Drop support `:first-child` and `:last-child`
|
6
|
+
* Redefine goto scope in nql
|
7
|
+
* Fix shift/reduce conflict
|
8
|
+
|
9
|
+
## 1.3.1 (2022-05-14)
|
10
|
+
|
11
|
+
* Add `add_comma` option to remove extra comma
|
12
|
+
|
13
|
+
## 1.3.0 (2022-05-12)
|
14
|
+
|
15
|
+
* Support `*=`, `^=` and `$=` operators
|
16
|
+
* Simplify RELATIONSHIP parser
|
17
|
+
* Rewrite compiler, let the selector to query nodes
|
18
|
+
|
3
19
|
## 1.2.1 (2022-05-01)
|
4
20
|
|
5
21
|
* Selector always after a node type in NQL
|
data/Gemfile
CHANGED
@@ -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}"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Synvert::Core::NodeQuery::Compiler
|
4
|
+
# BasicSelector used to match nodes, it combines by node type and/or attribute list.
|
5
|
+
class BasicSelector
|
6
|
+
# Initialize a BasicSelector.
|
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 = [".#{@node_type}"]
|
24
|
+
result << @attribute_list.to_s if @attribute_list
|
25
|
+
result.join('')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -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,105 +18,24 @@ 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
|
-
# @param descendant_match [Boolean] whether to match in descendant node
|
34
24
|
# @return [Array<Parser::AST::Node>] matching nodes.
|
35
|
-
def query_nodes(node
|
36
|
-
|
37
|
-
|
38
|
-
return find_nodes_by_relationship(node) if @relationship
|
39
|
-
|
40
|
-
matching_nodes = find_nodes_without_relationship(node, descendant_match)
|
25
|
+
def query_nodes(node)
|
26
|
+
matching_nodes = @selector.query_nodes(node)
|
41
27
|
return matching_nodes if @rest.nil?
|
42
28
|
|
43
|
-
matching_nodes.
|
44
|
-
|
29
|
+
matching_nodes.flat_map do |matching_node|
|
30
|
+
@rest.query_nodes(matching_node)
|
31
|
+
end
|
45
32
|
end
|
46
33
|
|
47
34
|
def to_s
|
48
|
-
return @selector.to_s unless @rest
|
49
|
-
|
50
35
|
result = []
|
51
36
|
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
|
37
|
+
result << @rest.to_s if @rest
|
61
38
|
result.join(' ')
|
62
39
|
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
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
|
-
|
114
|
-
return [node] unless @selector
|
115
|
-
|
116
|
-
nodes = []
|
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)
|
124
|
-
end
|
125
40
|
end
|
126
41
|
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,53 +4,110 @@ 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 basic_selector [Synvert::Core::NodeQuery::Compiler::BasicSelector] the simple selector
|
8
11
|
# @param attribute_list [Synvert::Core::NodeQuery::Compiler::AttributeList] the attribute list
|
9
|
-
# @param index [Integer] the index
|
10
12
|
# @param pseudo_class [String] the pseudo class, can be <code>has</code> or <code>not_has</code>
|
11
|
-
# @param
|
12
|
-
def initialize(
|
13
|
-
@
|
14
|
-
@
|
15
|
-
@
|
13
|
+
# @param pseudo_selector [Synvert::Core::NodeQuery::Compiler::Expression] the pseudo selector
|
14
|
+
def initialize(goto_scope: nil, relationship: nil, rest: nil, basic_selector: nil, pseudo_class: nil, pseudo_selector: nil)
|
15
|
+
@goto_scope = goto_scope
|
16
|
+
@relationship = relationship
|
17
|
+
@rest = rest
|
18
|
+
@basic_selector = basic_selector
|
16
19
|
@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]] : []
|
20
|
+
@pseudo_selector = pseudo_selector
|
25
21
|
end
|
26
22
|
|
27
23
|
# Check if node matches the selector.
|
28
24
|
# @param node [Parser::AST::Node] the node
|
29
|
-
def match?(node
|
30
|
-
|
31
|
-
|
32
|
-
|
25
|
+
def match?(node)
|
26
|
+
node.is_a?(::Parser::AST::Node) && (!@basic_selector || @basic_selector.match?(node)) && match_pseudo_class?(node)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Query nodes by the selector.
|
30
|
+
#
|
31
|
+
# * If relationship is nil, it will match in all recursive child nodes and return matching nodes.
|
32
|
+
# * If relationship is decendant, it will match in all recursive child nodes.
|
33
|
+
# * If relationship is child, it will match in direct child nodes.
|
34
|
+
# * If relationship is next sibling, it try to match next sibling node.
|
35
|
+
# * If relationship is subsequent sibling, it will match in all sibling nodes.
|
36
|
+
# @param node [Parser::AST::Node] node to match
|
37
|
+
# @return [Array<Parser::AST::Node>] matching nodes.
|
38
|
+
def query_nodes(node)
|
39
|
+
return find_nodes_by_relationship(node) if @relationship
|
40
|
+
|
41
|
+
if node.is_a?(::Array)
|
42
|
+
return node.flat_map { |child_node| query_nodes(child_node) }
|
43
|
+
end
|
44
|
+
|
45
|
+
return find_nodes_by_goto_scope(node) if @goto_scope
|
46
|
+
|
47
|
+
nodes = []
|
48
|
+
nodes << node if match?(node)
|
49
|
+
if @basic_selector
|
50
|
+
node.recursive_children do |child_node|
|
51
|
+
nodes << child_node if match?(child_node)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
nodes
|
33
55
|
end
|
34
56
|
|
35
57
|
def to_s
|
36
58
|
result = []
|
37
|
-
result << "
|
38
|
-
result << @
|
39
|
-
result <<
|
40
|
-
if @
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
59
|
+
result << "#{@goto_scope} " if @goto_scope
|
60
|
+
result << "#{@relationship} " if @relationship
|
61
|
+
result << @rest.to_s if @rest
|
62
|
+
result << @basic_selector.to_s if @basic_selector
|
63
|
+
result << ":#{@pseudo_class}(#{@pseudo_selector})" if @pseudo_class
|
64
|
+
result.join('')
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# Find nodes by @goto_scope
|
70
|
+
# @param node [Parser::AST::Node] node to match
|
71
|
+
def find_nodes_by_goto_scope(node)
|
72
|
+
@goto_scope.split('.').each { |scope| node = node.send(scope) }
|
73
|
+
@rest.query_nodes(node)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Find ndoes by @relationship
|
77
|
+
# @param node [Parser::AST::Node] node to match
|
78
|
+
def find_nodes_by_relationship(node)
|
79
|
+
nodes = []
|
80
|
+
case @relationship
|
81
|
+
when '>'
|
82
|
+
if node.is_a?(::Array)
|
83
|
+
node.each do |child_node|
|
84
|
+
nodes << child_node if @rest.match?(child_node)
|
51
85
|
end
|
86
|
+
else
|
87
|
+
node.children.each do |child_node|
|
88
|
+
nodes << child_node if @rest.match?(child_node)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
when '+'
|
92
|
+
next_sibling = node.siblings.first
|
93
|
+
nodes << next_sibling if @rest.match?(next_sibling)
|
94
|
+
when '~'
|
95
|
+
node.siblings.each do |sibling_node|
|
96
|
+
nodes << sibling_node if @rest.match?(sibling_node)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
nodes
|
100
|
+
end
|
101
|
+
|
102
|
+
def match_pseudo_class?(node)
|
103
|
+
case @pseudo_class
|
104
|
+
when 'has'
|
105
|
+
!@pseudo_selector.query_nodes(node).empty?
|
106
|
+
when 'not_has'
|
107
|
+
@pseudo_selector.query_nodes(node).empty?
|
108
|
+
else
|
109
|
+
true
|
52
110
|
end
|
53
|
-
result.join('')
|
54
111
|
end
|
55
112
|
end
|
56
113
|
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 :BasicSelector, 'synvert/core/node_query/compiler/basic_selector'
|
11
12
|
autoload :AttributeList, 'synvert/core/node_query/compiler/attribute_list'
|
12
13
|
autoload :Attribute, 'synvert/core/node_query/compiler/attribute'
|
13
14
|
|
@@ -7,8 +7,6 @@ macros
|
|
7
7
|
CLOSE_ARRAY /\)/
|
8
8
|
OPEN_SELECTOR /\(/
|
9
9
|
CLOSE_SELECTOR /\)/
|
10
|
-
OPEN_GOTO_SCOPE /</
|
11
|
-
CLOSE_GOTO_SCOPE />/
|
12
10
|
OPEN_DYNAMIC_ATTRIBUTE /{{/
|
13
11
|
CLOSE_DYNAMIC_ATTRIBUTE /}}/
|
14
12
|
NODE_TYPE /\.[a-z]+/
|
@@ -22,42 +20,38 @@ macros
|
|
22
20
|
REGEXP /\/(#{REGEXP_BODY})(?<!\\)\/([imxo]*)/
|
23
21
|
SYMBOL /:[\w!\?<>=]+/
|
24
22
|
TRUE /true/
|
25
|
-
SINGLE_QUOTE_STRING /'
|
26
|
-
DOUBLE_QUOTE_STRING /"
|
23
|
+
SINGLE_QUOTE_STRING /'.*?'/
|
24
|
+
DOUBLE_QUOTE_STRING /".*?"/
|
27
25
|
|
28
26
|
rules
|
29
27
|
|
30
28
|
# [:state] pattern [actions]
|
31
29
|
/\s+/
|
32
|
-
/:first-child/ { [:tINDEX, 0] }
|
33
|
-
/:last-child/ { [:tINDEX, -1] }
|
34
|
-
/:nth-child\(\d+\)/ { [:tINDEX, text.sub(':nth-child(', '').to_i - 1] }
|
35
|
-
/:nth-last-child\(\d+\)/ { [:tINDEX, -text.sub(':nth-last-child(', '').to_i] }
|
36
30
|
/:has/ { [:tPSEUDO_CLASS, text[1..-1]] }
|
37
31
|
/:not_has/ { [:tPSEUDO_CLASS, text[1..-1]] }
|
38
32
|
/#{NODE_TYPE}/ { [:tNODE_TYPE, text[1..]] }
|
39
|
-
|
40
|
-
|
41
|
-
|
33
|
+
/#{IDENTIFIER}/ { [:tGOTO_SCOPE, text] }
|
34
|
+
/>/ { [:tRELATIONSHIP, text] }
|
35
|
+
/~/ { [:tRELATIONSHIP, text] }
|
36
|
+
/\+/ { [:tRELATIONSHIP, text] }
|
42
37
|
/#{OPEN_SELECTOR}/ { [:tOPEN_SELECTOR, text] }
|
43
38
|
/#{CLOSE_SELECTOR}/ { [:tCLOSE_SELECTOR, text] }
|
44
|
-
/#{OPEN_GOTO_SCOPE}/ { @state = :GOTO_SCOPE; [:tOPEN_GOTO_SCOPE, text] }
|
45
39
|
/#{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] }
|
49
40
|
:KEY /\s+/
|
50
|
-
:KEY
|
51
|
-
:KEY
|
52
|
-
:KEY
|
53
|
-
:KEY
|
54
|
-
:KEY
|
55
|
-
:KEY
|
56
|
-
:KEY
|
57
|
-
:KEY
|
58
|
-
:KEY
|
59
|
-
:KEY
|
60
|
-
:KEY
|
41
|
+
:KEY /\^=/ { @state = :VALUE; [:tOPERATOR, '^='] }
|
42
|
+
:KEY /\$=/ { @state = :VALUE; [:tOPERATOR, '$='] }
|
43
|
+
:KEY /\*=/ { @state = :VALUE; [:tOPERATOR, '*='] }
|
44
|
+
:KEY /!=/ { @state = :VALUE; [:tOPERATOR, '!='] }
|
45
|
+
:KEY /=~/ { @state = :VALUE; [:tOPERATOR, '=~'] }
|
46
|
+
:KEY /!~/ { @state = :VALUE; [:tOPERATOR, '!~'] }
|
47
|
+
:KEY />=/ { @state = :VALUE; [:tOPERATOR, '>='] }
|
48
|
+
:KEY /<=/ { @state = :VALUE; [:tOPERATOR, '<='] }
|
49
|
+
:KEY />/ { @state = :VALUE; [:tOPERATOR, '>'] }
|
50
|
+
:KEY /</ { @state = :VALUE; [:tOPERATOR, '<'] }
|
51
|
+
:KEY /=/ { @state = :VALUE; [:tOPERATOR, '=='] }
|
52
|
+
:KEY /includes/i { @state = :VALUE; [:tOPERATOR, 'includes'] }
|
53
|
+
:KEY /not in/i { @state = :VALUE; [:tOPERATOR, 'not_in'] }
|
54
|
+
:KEY /in/i { @state = :VALUE; [:tOPERATOR, 'in'] }
|
61
55
|
:KEY /#{IDENTIFIER}/ { [:tKEY, text] }
|
62
56
|
:VALUE /\s+/
|
63
57
|
:VALUE /\[\]=/ { [:tIDENTIFIER_VALUE, text] }
|