json_p3 0.4.0 → 1.0.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
- checksums.yaml.gz.sig +0 -0
- data/.rubocop.yml +26 -7
- data/.ruby-version +1 -1
- data/CHANGELOG.md +58 -0
- data/README.md +125 -123
- data/Rakefile +3 -3
- data/certs/jgrp.pem +21 -21
- data/lib/json_p3/errors.rb +51 -43
- data/lib/json_p3/patch/op.rb +23 -0
- data/lib/json_p3/patch/op_add.rb +51 -0
- data/lib/json_p3/patch/op_copy.rb +64 -0
- data/lib/json_p3/patch/op_move.rb +74 -0
- data/lib/json_p3/patch/op_remove.rb +56 -0
- data/lib/json_p3/patch/op_replace.rb +54 -0
- data/lib/json_p3/patch/op_test.rb +31 -0
- data/lib/json_p3/patch.rb +15 -330
- data/lib/json_p3/path/environment.rb +113 -0
- data/lib/json_p3/path/filter.rb +463 -0
- data/lib/json_p3/path/function.rb +12 -0
- data/lib/json_p3/path/function_extensions/count.rb +15 -0
- data/lib/json_p3/path/function_extensions/length.rb +17 -0
- data/lib/json_p3/path/function_extensions/match.rb +62 -0
- data/lib/json_p3/path/function_extensions/pattern.rb +42 -0
- data/lib/json_p3/path/function_extensions/search.rb +44 -0
- data/lib/json_p3/path/function_extensions/value.rb +15 -0
- data/lib/json_p3/path/lexer.rb +220 -0
- data/lib/json_p3/path/node.rb +48 -0
- data/lib/json_p3/path/parser.rb +676 -0
- data/lib/json_p3/path/query.rb +74 -0
- data/lib/json_p3/path/segment.rb +172 -0
- data/lib/json_p3/path/selector.rb +304 -0
- data/lib/json_p3/path/serialize.rb +16 -0
- data/lib/json_p3/path/unescape.rb +134 -0
- data/lib/json_p3/pointer.rb +15 -76
- data/lib/json_p3/relative_pointer.rb +69 -0
- data/lib/json_p3/version.rb +1 -1
- data/lib/json_p3.rb +50 -13
- data/sig/json_p3/cache.rbs +21 -0
- data/sig/json_p3/errors.rbs +55 -0
- data/sig/json_p3/patch.rbs +145 -0
- data/sig/json_p3/path/environment.rbs +81 -0
- data/sig/json_p3/path/filter.rbs +196 -0
- data/sig/json_p3/path/function.rbs +94 -0
- data/sig/json_p3/path/lexer.rbs +62 -0
- data/sig/json_p3/path/node.rbs +46 -0
- data/sig/json_p3/path/parser.rbs +92 -0
- data/sig/json_p3/path/query.rbs +47 -0
- data/sig/json_p3/path/segment.rbs +54 -0
- data/sig/json_p3/path/selector.rbs +100 -0
- data/sig/json_p3/path/serialize.rbs +9 -0
- data/sig/json_p3/path/unescape.rbs +12 -0
- data/sig/json_p3/pointer.rbs +64 -0
- data/sig/json_p3/relative_pointer.rbs +30 -0
- data/sig/json_p3.rbs +24 -1313
- data.tar.gz.sig +0 -0
- metadata +66 -46
- metadata.gz.sig +0 -0
- data/lib/json_p3/environment.rb +0 -111
- data/lib/json_p3/filter.rb +0 -459
- data/lib/json_p3/function.rb +0 -10
- data/lib/json_p3/function_extensions/count.rb +0 -15
- data/lib/json_p3/function_extensions/length.rb +0 -17
- data/lib/json_p3/function_extensions/match.rb +0 -62
- data/lib/json_p3/function_extensions/pattern.rb +0 -39
- data/lib/json_p3/function_extensions/search.rb +0 -44
- data/lib/json_p3/function_extensions/value.rb +0 -15
- data/lib/json_p3/lexer.rb +0 -419
- data/lib/json_p3/node.rb +0 -44
- data/lib/json_p3/parser.rb +0 -553
- data/lib/json_p3/path.rb +0 -72
- data/lib/json_p3/segment.rb +0 -158
- data/lib/json_p3/selector.rb +0 -306
- data/lib/json_p3/serialize.rb +0 -13
- data/lib/json_p3/token.rb +0 -36
- data/lib/json_p3/unescape.rb +0 -112
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "node"
|
|
4
|
+
|
|
5
|
+
module JSONP3
|
|
6
|
+
module Path
|
|
7
|
+
# A compiled JSONPath expression ready to be applied to JSON-like values.
|
|
8
|
+
class Query
|
|
9
|
+
def initialize(env, segments)
|
|
10
|
+
@env = env
|
|
11
|
+
@segments = segments
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_s
|
|
15
|
+
"$#{@segments.join}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Apply this JSONPath expression to JSON-like value _root_.
|
|
19
|
+
# @param root [Array, Hash, String, Integer, nil] the root JSON-like value to apply this query to.
|
|
20
|
+
# @return [Array<Node>] the sequence of nodes found while applying this query to _root_.
|
|
21
|
+
def find(root)
|
|
22
|
+
nodes = [Node.new(root, [], root)]
|
|
23
|
+
@segments.each { |segment| nodes = segment.resolve(nodes) }
|
|
24
|
+
NodeList.new(nodes)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
alias apply find
|
|
28
|
+
|
|
29
|
+
# Apply this JSONPath expression to JSON-like value _root_.
|
|
30
|
+
# @param root [Array, Hash, String, Integer, nil] the root JSON-like value to apply this query to.
|
|
31
|
+
# @return [Enumerable<Node>] the sequence of nodes found while applying this query to _root_.
|
|
32
|
+
def find_enum(root)
|
|
33
|
+
nodes = [Node.new(root, [], root)] # : Enumerable[Node]
|
|
34
|
+
@segments.each { |segment| nodes = segment.resolve_enum(nodes) }
|
|
35
|
+
nodes
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Return the first node from applying this JSONPath expression to JSON-like value _root_.
|
|
39
|
+
# @param root [Array, Hash, String, Integer, nil] the root JSON-like value to apply this query to.
|
|
40
|
+
# @return [Node | nil] the first available node or nil if there were no matches.
|
|
41
|
+
def match(root)
|
|
42
|
+
find_enum(root).first
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Return `true` if this query results in at least one node, or `false` otherwise.
|
|
46
|
+
# @param root [Array, Hash, String, Integer, nil] the root JSON-like value to apply this query to.
|
|
47
|
+
# @return [bool] `true` if this query results in at least one node, or `false` otherwise.
|
|
48
|
+
def match?(root)
|
|
49
|
+
!find_enum(root).first.nil?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Return the first node from applying this JSONPath expression to JSON-like value _root_.
|
|
53
|
+
# @param root [Array, Hash, String, Integer, nil] the root JSON-like value to apply this query to.
|
|
54
|
+
# @return [Node | nil] the first available node or nil if there were no matches.
|
|
55
|
+
def first(root)
|
|
56
|
+
find_enum(root).first
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Return _true_ if this JSONPath expression is a singular query.
|
|
60
|
+
def singular?
|
|
61
|
+
@segments.each do |segment|
|
|
62
|
+
return false if segment.instance_of? DescendantSegment
|
|
63
|
+
return false unless segment.selectors.length == 1 && segment.selectors[0].singular?
|
|
64
|
+
end
|
|
65
|
+
true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Return _true_ if this JSONPath expression has no segments.
|
|
69
|
+
def empty?
|
|
70
|
+
@segments.empty?
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSONP3
|
|
4
|
+
module Path
|
|
5
|
+
# Base class for all JSONPath segments.
|
|
6
|
+
class Segment
|
|
7
|
+
# @dynamic token, selectors
|
|
8
|
+
attr_reader :token, :selectors
|
|
9
|
+
|
|
10
|
+
def initialize(env, token, selectors)
|
|
11
|
+
@env = env
|
|
12
|
+
@token = token
|
|
13
|
+
@selectors = selectors
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Select the children of each node in _nodes_.
|
|
17
|
+
# @return [Array<Node>]
|
|
18
|
+
def resolve(_nodes)
|
|
19
|
+
raise "segments must implement resolve(nodes)"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Select the children of each node in _nodes_.
|
|
23
|
+
# @return [Enumerable<Node>]
|
|
24
|
+
def resolve_enum(_nodes)
|
|
25
|
+
raise "segments must implement resolve_enum(nodes)"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The child selection segment.
|
|
30
|
+
class ChildSegment < Segment
|
|
31
|
+
def resolve(nodes)
|
|
32
|
+
rv = [] # : Array[Node]
|
|
33
|
+
nodes.each do |node|
|
|
34
|
+
@selectors.each do |selector|
|
|
35
|
+
rv.concat selector.resolve(node)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
rv
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def resolve_enum(nodes)
|
|
42
|
+
Enumerator.new do |yielder|
|
|
43
|
+
nodes.each do |node|
|
|
44
|
+
@selectors.each do |selector|
|
|
45
|
+
selector.resolve(node).each do |item|
|
|
46
|
+
yielder << item
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def to_s
|
|
54
|
+
"[#{@selectors.join(", ")}]"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def ==(other)
|
|
58
|
+
self.class == other.class &&
|
|
59
|
+
@selectors == other.selectors &&
|
|
60
|
+
@token == other.token
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
alias eql? ==
|
|
64
|
+
|
|
65
|
+
def hash
|
|
66
|
+
[@selectors, @token].hash
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# The descendant segment
|
|
71
|
+
class DescendantSegment < Segment
|
|
72
|
+
def resolve(nodes)
|
|
73
|
+
rv = [] #: Array[Node]
|
|
74
|
+
nodes.each do |node|
|
|
75
|
+
visit(node).each do |descendant|
|
|
76
|
+
@selectors.each do |selector|
|
|
77
|
+
rv.concat selector.resolve(descendant)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
rv
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def resolve_enum(nodes)
|
|
85
|
+
Enumerator.new do |yielder|
|
|
86
|
+
nodes.each do |node|
|
|
87
|
+
visit_enum(node).each do |descendant|
|
|
88
|
+
@selectors.each do |selector|
|
|
89
|
+
selector.resolve(descendant).each do |item|
|
|
90
|
+
yielder << item
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def to_s
|
|
99
|
+
"..[#{@selectors.join(", ")}]"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def ==(other)
|
|
103
|
+
self.class == other.class &&
|
|
104
|
+
@selectors == other.selectors &&
|
|
105
|
+
@token == other.token
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
alias eql? ==
|
|
109
|
+
|
|
110
|
+
def hash
|
|
111
|
+
["..", @selectors, @token].hash
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
protected
|
|
115
|
+
|
|
116
|
+
def visit(node, depth = 1)
|
|
117
|
+
if depth > @env.class::MAX_RECURSION_DEPTH
|
|
118
|
+
raise JSONP3::Path::RecursionError.new(
|
|
119
|
+
"recursion limit exceeded",
|
|
120
|
+
@token,
|
|
121
|
+
""
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
rv = [node]
|
|
126
|
+
|
|
127
|
+
if node.value.is_a? Array
|
|
128
|
+
node.value.each_with_index do |value, i|
|
|
129
|
+
child = Node.new(value, [node.location, i], node.root)
|
|
130
|
+
rv.concat visit(child, depth + 1)
|
|
131
|
+
end
|
|
132
|
+
elsif node.value.is_a? Hash
|
|
133
|
+
node.value.each do |key, value|
|
|
134
|
+
child = Node.new(value, [node.location, key], node.root)
|
|
135
|
+
rv.concat visit(child, depth + 1)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
rv
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def visit_enum(node, depth = 1)
|
|
143
|
+
if depth > @env.class::MAX_RECURSION_DEPTH
|
|
144
|
+
raise JSONP3::Path::RecursionError.new(
|
|
145
|
+
"recursion limit exceeded",
|
|
146
|
+
@token,
|
|
147
|
+
""
|
|
148
|
+
)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
Enumerator.new do |yielder|
|
|
152
|
+
yielder << node
|
|
153
|
+
if node.value.is_a? Array
|
|
154
|
+
node.value.each_with_index do |value, i|
|
|
155
|
+
child = Node.new(value, [node.location, i], node.root)
|
|
156
|
+
visit_enum(child, depth + 1).each do |item|
|
|
157
|
+
yielder << item
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
elsif node.value.is_a? Hash
|
|
161
|
+
node.value.each do |key, value|
|
|
162
|
+
child = Node.new(value, [node.location, key], node.root)
|
|
163
|
+
visit_enum(child, depth + 1).each do |item|
|
|
164
|
+
yielder << item
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSONP3
|
|
4
|
+
module Path
|
|
5
|
+
# Base class for all JSONPath selectors
|
|
6
|
+
class Selector
|
|
7
|
+
attr_reader :token
|
|
8
|
+
|
|
9
|
+
def initialize(env, token)
|
|
10
|
+
@env = env
|
|
11
|
+
@token = token
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Apply this selector to _node_.
|
|
15
|
+
# @return [Array<Node>]
|
|
16
|
+
def resolve(_node)
|
|
17
|
+
raise "selectors must implement resolve(node)"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Apply this selector to _node_.
|
|
21
|
+
# @return [Enumerable<Node>]
|
|
22
|
+
def resolve_enum(node)
|
|
23
|
+
resolve(node)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Return true if this selector is a singular selector.
|
|
27
|
+
def singular?
|
|
28
|
+
false
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The name selector select values from hashes given a key.
|
|
33
|
+
class NameSelector < Selector
|
|
34
|
+
attr_reader :name
|
|
35
|
+
|
|
36
|
+
def initialize(env, token, name)
|
|
37
|
+
super(env, token)
|
|
38
|
+
@name = name
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def resolve(node)
|
|
42
|
+
if node.value.is_a?(Hash) && node.value.key?(@name)
|
|
43
|
+
[node.new_child(node.value[@name], @name)]
|
|
44
|
+
else
|
|
45
|
+
[]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def singular?
|
|
50
|
+
true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def to_s
|
|
54
|
+
JSONP3::Path.canonical_string(@name)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def ==(other)
|
|
58
|
+
self.class == other.class &&
|
|
59
|
+
@name == other.name &&
|
|
60
|
+
@token == other.token
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
alias eql? ==
|
|
64
|
+
|
|
65
|
+
def hash
|
|
66
|
+
[@name, @token].hash
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# This non-standard name selector selects values from hashes given a string or
|
|
71
|
+
# symbol key.
|
|
72
|
+
class SymbolNameSelector < NameSelector
|
|
73
|
+
def initialize(env, token, name)
|
|
74
|
+
super
|
|
75
|
+
@sym = @name.to_sym
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def resolve(node)
|
|
79
|
+
if node.value.is_a?(Hash)
|
|
80
|
+
if node.value.key?(@name)
|
|
81
|
+
[node.new_child(node.value[@name], @name)]
|
|
82
|
+
elsif node.value.key?(@sym)
|
|
83
|
+
[node.new_child(node.value[@sym], @name)]
|
|
84
|
+
else
|
|
85
|
+
[]
|
|
86
|
+
end
|
|
87
|
+
else
|
|
88
|
+
[]
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# The index selector selects values from arrays given an index.
|
|
94
|
+
class IndexSelector < Selector
|
|
95
|
+
attr_reader :index
|
|
96
|
+
|
|
97
|
+
def initialize(env, token, index)
|
|
98
|
+
super(env, token)
|
|
99
|
+
@index = index
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def resolve(node)
|
|
103
|
+
if node.value.is_a?(Array)
|
|
104
|
+
norm_index = normalize(@index, node.value.length)
|
|
105
|
+
return [] if norm_index.negative? || norm_index >= node.value.length
|
|
106
|
+
|
|
107
|
+
[node.new_child(node.value[@index], norm_index)]
|
|
108
|
+
else
|
|
109
|
+
[]
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def singular?
|
|
114
|
+
true
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def to_s
|
|
118
|
+
@index.to_s
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def ==(other)
|
|
122
|
+
self.class == other.class &&
|
|
123
|
+
@index == other.index &&
|
|
124
|
+
@token == other.token
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
alias eql? ==
|
|
128
|
+
|
|
129
|
+
def hash
|
|
130
|
+
[@index, @token].hash
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def normalize(index, length)
|
|
136
|
+
index.negative? && length >= index.abs ? length + index : index
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# The wildcard selector selects all elements from an array or values from a hash.
|
|
141
|
+
class WildcardSelector < Selector
|
|
142
|
+
def resolve(node)
|
|
143
|
+
if node.value.is_a? Hash
|
|
144
|
+
node.value.map { |k, v| node.new_child(v, k) }
|
|
145
|
+
elsif node.value.is_a? Array
|
|
146
|
+
node.value.map.with_index { |e, i| node.new_child(e, i) }
|
|
147
|
+
else
|
|
148
|
+
[]
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def resolve_enum(node)
|
|
153
|
+
if node.value.is_a? Hash
|
|
154
|
+
Enumerator.new do |yielder|
|
|
155
|
+
node.value.each do |k, v|
|
|
156
|
+
yielder << node.new_child(v, k)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
elsif node.value.is_a? Array
|
|
160
|
+
Enumerator.new do |yielder|
|
|
161
|
+
node.value.each.with_index do |e, i|
|
|
162
|
+
yielder << node.new_child(e, i)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
else
|
|
166
|
+
[]
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def to_s
|
|
171
|
+
"*"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def ==(other)
|
|
175
|
+
self.class == other.class && @token == other.token
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
alias eql? ==
|
|
179
|
+
|
|
180
|
+
def hash
|
|
181
|
+
@token.hash
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# The slice selector selects a range of elements from an array.
|
|
186
|
+
class SliceSelector < Selector
|
|
187
|
+
attr_reader :start, :stop, :step
|
|
188
|
+
|
|
189
|
+
def initialize(env, token, start, stop, step)
|
|
190
|
+
super(env, token)
|
|
191
|
+
@start = start
|
|
192
|
+
@stop = stop
|
|
193
|
+
@step = step || 1
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def resolve(node)
|
|
197
|
+
return [] unless node.value.is_a?(Array)
|
|
198
|
+
|
|
199
|
+
length = node.value.length
|
|
200
|
+
return [] if length.zero? || @step.zero?
|
|
201
|
+
|
|
202
|
+
normalized_start = if @start.nil?
|
|
203
|
+
@step.negative? ? length - 1 : 0
|
|
204
|
+
elsif @start&.negative?
|
|
205
|
+
[length + (@start || raise), 0].max
|
|
206
|
+
else
|
|
207
|
+
[@start || raise, length - 1].min
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
normalized_stop = if @stop.nil?
|
|
211
|
+
@step.negative? ? -1 : length
|
|
212
|
+
elsif @stop&.negative?
|
|
213
|
+
[length + (@stop || raise), -1].max
|
|
214
|
+
else
|
|
215
|
+
[@stop || raise, length].min
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
(normalized_start...normalized_stop).step(@step).map { |i| node.new_child(node.value[i], i) }
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def to_s
|
|
222
|
+
start = @start || ""
|
|
223
|
+
stop = @stop || ""
|
|
224
|
+
step = @step || 1
|
|
225
|
+
"#{start}:#{stop}:#{step}"
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def ==(other)
|
|
229
|
+
self.class == other.class &&
|
|
230
|
+
@start == other.start &&
|
|
231
|
+
@stop == other.stop &&
|
|
232
|
+
@step == other.step &&
|
|
233
|
+
@token == other.token
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
alias eql? ==
|
|
237
|
+
|
|
238
|
+
def hash
|
|
239
|
+
[@start, @stop, @step, @token].hash
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Select array elements or hash values according to a filter expression.
|
|
244
|
+
class FilterSelector < Selector
|
|
245
|
+
attr_reader :expression
|
|
246
|
+
|
|
247
|
+
def initialize(env, token, expression)
|
|
248
|
+
super(env, token)
|
|
249
|
+
@expression = expression
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def resolve(node)
|
|
253
|
+
# @type var nodes: Array[Node]
|
|
254
|
+
nodes = []
|
|
255
|
+
|
|
256
|
+
if node.value.is_a?(Array)
|
|
257
|
+
node.value.each_with_index do |e, i|
|
|
258
|
+
context = FilterContext.new(@env, e, node.root)
|
|
259
|
+
nodes << node.new_child(e, i) if @expression.evaluate(context)
|
|
260
|
+
end
|
|
261
|
+
elsif node.value.is_a?(Hash)
|
|
262
|
+
node.value.each_pair do |k, v|
|
|
263
|
+
context = FilterContext.new(@env, v, node.root)
|
|
264
|
+
nodes << node.new_child(v, k) if @expression.evaluate(context)
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
nodes
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def resolve_enum(node)
|
|
272
|
+
Enumerator.new do |yielder|
|
|
273
|
+
if node.value.is_a?(Array)
|
|
274
|
+
node.value.each_with_index do |e, i|
|
|
275
|
+
context = FilterContext.new(@env, e, node.root)
|
|
276
|
+
yielder << node.new_child(e, i) if @expression.evaluate(context)
|
|
277
|
+
end
|
|
278
|
+
elsif node.value.is_a?(Hash)
|
|
279
|
+
node.value.each_pair do |k, v|
|
|
280
|
+
context = FilterContext.new(@env, v, node.root)
|
|
281
|
+
yielder << node.new_child(v, k) if @expression.evaluate(context)
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def to_s
|
|
288
|
+
"?#{@expression}"
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def ==(other)
|
|
292
|
+
self.class == other.class &&
|
|
293
|
+
@expression == other.start &&
|
|
294
|
+
@token == other.token
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
alias eql? ==
|
|
298
|
+
|
|
299
|
+
def hash
|
|
300
|
+
[@expression, @token].hash
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module JSONP3
|
|
6
|
+
# JSONPath query expressions.
|
|
7
|
+
module Path
|
|
8
|
+
TRANS = { "\\\"" => "\"", "'" => "\\'" }.freeze
|
|
9
|
+
|
|
10
|
+
# Return _value_ formatted as a canonical string literal.
|
|
11
|
+
# @param value [String]
|
|
12
|
+
def self.canonical_string(value)
|
|
13
|
+
"'#{(JSON.dump(value)[1..-2] || raise).gsub(/('|\\")/, TRANS)}'"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "strscan"
|
|
4
|
+
|
|
5
|
+
module JSONP3
|
|
6
|
+
# JSONPath query expressions.
|
|
7
|
+
module Path
|
|
8
|
+
RE_SLASH_U = /\\u([0-9a-fA-F]{4})/
|
|
9
|
+
|
|
10
|
+
# Replace escape sequences with their equivalent Unicode code point.
|
|
11
|
+
def self.unescape(value, token, query)
|
|
12
|
+
unescaped = String.new(encoding: "UTF-8")
|
|
13
|
+
scanner = StringScanner.new(value)
|
|
14
|
+
|
|
15
|
+
until scanner.eos?
|
|
16
|
+
if scanner.scan(RE_SLASH_U)
|
|
17
|
+
code_point = (scanner.captures&.first || raise).to_i(16)
|
|
18
|
+
|
|
19
|
+
if low_surrogate?(code_point)
|
|
20
|
+
raise JSONP3::Path::SyntaxError.new(
|
|
21
|
+
"unexpected low surrogate",
|
|
22
|
+
token,
|
|
23
|
+
query
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
if high_surrogate?(code_point)
|
|
28
|
+
unless scanner.scan(RE_SLASH_U)
|
|
29
|
+
raise JSONP3::Path::SyntaxError.new(
|
|
30
|
+
"expected a low surrogate",
|
|
31
|
+
token,
|
|
32
|
+
query
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
low_surrogate = (scanner.captures&.first || raise).to_i(16)
|
|
37
|
+
|
|
38
|
+
unless low_surrogate?(low_surrogate)
|
|
39
|
+
raise JSONP3::Path::SyntaxError.new(
|
|
40
|
+
"expected a low surrogate",
|
|
41
|
+
token,
|
|
42
|
+
query
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
code_point = 0x10000 + (
|
|
47
|
+
((code_point & 0x03FF) << 10) | (low_surrogate & 0x03FF)
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
if code_point <= 0x1f
|
|
52
|
+
raise JSONP3::Path::SyntaxError.new(
|
|
53
|
+
"invalid character #{code_point}",
|
|
54
|
+
token,
|
|
55
|
+
query
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
unescaped << code_point.chr(Encoding::UTF_8)
|
|
60
|
+
next
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
ch = scanner.getch
|
|
64
|
+
|
|
65
|
+
break if ch.nil?
|
|
66
|
+
|
|
67
|
+
unless ch == "\\"
|
|
68
|
+
if ch.ord <= 0x1f
|
|
69
|
+
raise JSONP3::Path::SyntaxError.new(
|
|
70
|
+
"invalid character #{ch.ord}",
|
|
71
|
+
token,
|
|
72
|
+
query
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
unescaped << ch
|
|
76
|
+
next
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
ch = scanner.getch
|
|
80
|
+
|
|
81
|
+
case ch
|
|
82
|
+
when "\""
|
|
83
|
+
if token.first == :token_single_quoted_esc_string
|
|
84
|
+
raise JSONP3::Path::SyntaxError.new(
|
|
85
|
+
"unexpected \\\" escape in single quoted string",
|
|
86
|
+
token,
|
|
87
|
+
query
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
unescaped << "\""
|
|
91
|
+
when "'"
|
|
92
|
+
if token.first == :token_double_quoted_esc_string
|
|
93
|
+
raise JSONP3::Path::SyntaxError.new(
|
|
94
|
+
"unexpected \\' escape in double quoted string",
|
|
95
|
+
token,
|
|
96
|
+
query
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
unescaped << "'"
|
|
100
|
+
when "\\"
|
|
101
|
+
unescaped << "\\"
|
|
102
|
+
when "/"
|
|
103
|
+
unescaped << "/"
|
|
104
|
+
when "b"
|
|
105
|
+
unescaped << "\x08"
|
|
106
|
+
when "f"
|
|
107
|
+
unescaped << "\x0c"
|
|
108
|
+
when "n"
|
|
109
|
+
unescaped << "\n"
|
|
110
|
+
when "r"
|
|
111
|
+
unescaped << "\r"
|
|
112
|
+
when "t"
|
|
113
|
+
unescaped << "\t"
|
|
114
|
+
when "u"
|
|
115
|
+
raise JSONP3::Path::SyntaxError.new("unexpected \\u escape sequence", token, query)
|
|
116
|
+
when nil
|
|
117
|
+
raise JSONP3::Path::SyntaxError.new("incomplete escape sequence", token, query)
|
|
118
|
+
else
|
|
119
|
+
raise JSONP3::Path::SyntaxError.new("unknown escape sequence", token, query)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
unescaped
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def self.high_surrogate?(code_point)
|
|
127
|
+
code_point.between?(0xD800, 0xDBFF)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def self.low_surrogate?(code_point)
|
|
131
|
+
code_point.between?(0xDC00, 0xDFFF)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|