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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3972bef6a4c4b41b4211e2f45d889c6e3c71b08b1b2b039ca3511a620f79ccf
4
- data.tar.gz: 7ddecb20188351e72e59b4a72c67a45d395638ae7a20d3c4d46f3fef0184de3c
3
+ metadata.gz: 56b98032247ef28e548b34dfd8b2b49bad8744227cd61b4f300c7111b701ff91
4
+ data.tar.gz: ea75a1e4f442041665dbd9babdc2f7e4b55c4c78c87facef856e903f2d8c96d2
5
5
  SHA512:
6
- metadata.gz: b89480e5d9d05fe0651b5cd9edd1a4408a52364d449d0632baf062dd40251670b8c7d98370fa831f6961454498e9fbfffa67011a4fabb45f21e6031933643c36
7
- data.tar.gz: c23844951b2938268289099f852f01d554e46a7aa80ad301011d05cc40a022864dd872a8097c71575a42e8770730dbfea2deea8d867af0412022506751cf2671
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
@@ -4,3 +4,12 @@ source 'https://rubygems.org'
4
4
 
5
5
  # Specify your gem's dependencies in synvert.gemspec
6
6
  gemspec
7
+
8
+ gem "guard"
9
+ gem "guard-rspec"
10
+ gem "guard-rake"
11
+ gem "oedipus_lex"
12
+ gem "racc"
13
+ gem "rake"
14
+ gem "rspec"
15
+ gem "rspec-mocks"
@@ -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 #{map(&:debug_info).join("\n")}, child_name: #{child_name}"
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 #{map(&:debug_info).join("\n")}, child_name: #{child_name}"
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[caller key left_value message name parent_class parent_const receiver right_value value].each do |method_name|
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
@@ -28,7 +28,7 @@ module Synvert::Core::NodeQuery::Compiler
28
28
  end
29
29
 
30
30
  def to_s
31
- [@value, @rest].compact.join(', ')
31
+ [@value, @rest].compact.join(' ')
32
32
  end
33
33
  end
34
34
  end
@@ -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}!=#{@value}"
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 :not_in
30
+ when 'not_in'
43
31
  "#{@key} not in (#{@value})"
44
- when :includes
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 = [:==, :!=, :includes]
7
- NUMBER_VALID_OPERATORS = [:==, :!=, :>, :>=, :<, :<=, :includes]
8
- ARRAY_VALID_OPERATORS = [:==, :!=, :in, :not_in]
9
- REGEXP_VALID_OPERATORS = [:=~, :!~]
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>:==</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
+ # @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 :in
42
- expected_value.any? { |expected| expected.match?(node, :==) }
43
- when :not_in
44
- expected_value.all? { |expected| expected.match?(node, :!=) }
45
- when :includes
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
- # @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)
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: true)
34
- matching_nodes = find_nodes_without_relationship(node, descendant_match: descendant_match)
35
- if @relationship.nil?
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
- expression_nodes = matching_nodes.map do |matching_node|
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
- case @relationship
65
- when :child then result << '>'
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
- def find_nodes_without_relationship(node, descendant_match: true)
76
- return [node] unless @selector
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
- nodes = []
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
@@ -31,7 +31,7 @@ module Synvert::Core::NodeQuery::Compiler
31
31
 
32
32
  # Get valid operators.
33
33
  def valid_operators
34
- SIMPLE_VALID_OPERATORS
34
+ STRING_VALID_OPERATORS
35
35
  end
36
36
 
37
37
  def to_s
@@ -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 == :=~ ? match : !match
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 node_type [String] the node type
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 pseudo_expression [Synvert::Core::NodeQuery::Compiler::Expression] the pseudo expression
12
- def initialize(node_type: nil, attribute_list: nil, index: nil, pseudo_class: nil, pseudo_expression: nil)
13
- @node_type = node_type
14
- @attribute_list = attribute_list
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
- @pseudo_expression = pseudo_expression
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, _operator = :==)
30
- (!@node_type || (node.is_a?(::Parser::AST::Node) && @node_type.to_sym == node.type)) &&
31
- (!@attribute_list || @attribute_list.match?(node)) &&
32
- (!@pseudo_class || (@pseudo_class == 'has' && @pseudo_expression.match?(node)) || (@pseudo_class == 'not_has' && !@pseudo_expression.match?(node)))
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
- str = ".#{@node_type}#{@attribute_list}"
37
- return str if !@index && !@pseudo_class
38
-
39
- return "#{str}:#{@pseudo_class}(#{@pseudo_expression})" if @pseudo_class
40
-
41
- case @index
42
- when 0
43
- str + ':first-child'
44
- when -1
45
- str + ':last-child'
46
- when (1..)
47
- str + ":nth-child(#{@index + 1})"
48
- else # ...-1
49
- str + ":nth-last-child(#{-@index})"
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
@@ -13,7 +13,7 @@ module Synvert::Core::NodeQuery::Compiler
13
13
 
14
14
  # Get valid operators.
15
15
  def valid_operators
16
- SIMPLE_VALID_OPERATORS
16
+ STRING_VALID_OPERATORS
17
17
  end
18
18
 
19
19
  def to_s
@@ -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