janeway-jsonpath 0.1.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 +7 -0
- data/LICENSE +7 -0
- data/README.md +43 -0
- data/bin/janeway +130 -0
- data/lib/janeway/ast/array_slice_selector.rb +104 -0
- data/lib/janeway/ast/binary_operator.rb +101 -0
- data/lib/janeway/ast/boolean.rb +24 -0
- data/lib/janeway/ast/child_segment.rb +48 -0
- data/lib/janeway/ast/current_node.rb +71 -0
- data/lib/janeway/ast/descendant_segment.rb +44 -0
- data/lib/janeway/ast/error.rb +10 -0
- data/lib/janeway/ast/expression.rb +55 -0
- data/lib/janeway/ast/filter_selector.rb +61 -0
- data/lib/janeway/ast/function.rb +40 -0
- data/lib/janeway/ast/helpers.rb +27 -0
- data/lib/janeway/ast/identifier.rb +35 -0
- data/lib/janeway/ast/index_selector.rb +27 -0
- data/lib/janeway/ast/name_selector.rb +52 -0
- data/lib/janeway/ast/null.rb +23 -0
- data/lib/janeway/ast/number.rb +21 -0
- data/lib/janeway/ast/query.rb +41 -0
- data/lib/janeway/ast/root_node.rb +50 -0
- data/lib/janeway/ast/selector.rb +32 -0
- data/lib/janeway/ast/string_type.rb +21 -0
- data/lib/janeway/ast/unary_operator.rb +26 -0
- data/lib/janeway/ast/wildcard_selector.rb +46 -0
- data/lib/janeway/error.rb +23 -0
- data/lib/janeway/functions/count.rb +39 -0
- data/lib/janeway/functions/length.rb +33 -0
- data/lib/janeway/functions/match.rb +69 -0
- data/lib/janeway/functions/search.rb +63 -0
- data/lib/janeway/functions/value.rb +47 -0
- data/lib/janeway/functions.rb +62 -0
- data/lib/janeway/interpreter.rb +644 -0
- data/lib/janeway/lexer.rb +514 -0
- data/lib/janeway/location.rb +3 -0
- data/lib/janeway/parser.rb +608 -0
- data/lib/janeway/token.rb +39 -0
- data/lib/janeway/version.rb +5 -0
- data/lib/janeway.rb +51 -0
- metadata +92 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Janeway
|
4
|
+
module Functions
|
5
|
+
# 2.4.7. search() Function Extension
|
6
|
+
# Parameters:
|
7
|
+
# 1. ValueType (string)
|
8
|
+
# 2. ValueType (string conforming to [RFC9485])
|
9
|
+
# Result:
|
10
|
+
# LogicalType
|
11
|
+
#
|
12
|
+
# The search() function extension provides a way to check whether a given
|
13
|
+
# string contains a substring that matches a given regular expression,
|
14
|
+
# which is in the form described in [RFC9485].
|
15
|
+
#
|
16
|
+
# $[?search(@.author, "[BR]ob")]
|
17
|
+
#
|
18
|
+
# Its arguments are instances of ValueType (possibly taken from a singular
|
19
|
+
# query, as for the first argument in the example above). If the first
|
20
|
+
# argument is not a string or the second argument is not a string
|
21
|
+
# conforming to [RFC9485], the result is LogicalFalse. Otherwise, the
|
22
|
+
# string that is the first argument is searched for a substring that
|
23
|
+
# matches the I-Regexp contained in the string that is the second argument;
|
24
|
+
# the result is LogicalTrue if at least one such substring exists and is
|
25
|
+
# LogicalFalse otherwise.
|
26
|
+
#
|
27
|
+
# tl;dr: How is this different from the match function?
|
28
|
+
# "match" must match the entire string, "search" matches a substring.
|
29
|
+
def parse_function_search
|
30
|
+
consume # function
|
31
|
+
raise "expect group_start token, found #{current}" unless current.type == :group_start
|
32
|
+
|
33
|
+
consume # (
|
34
|
+
|
35
|
+
# Read first parameter
|
36
|
+
parameters = []
|
37
|
+
parameters << parse_function_parameter
|
38
|
+
|
39
|
+
# Consume comma
|
40
|
+
if current.type == :union
|
41
|
+
consume # ,
|
42
|
+
else
|
43
|
+
raise "expect comma token, found #{current}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Read second parameter (the regexp)
|
47
|
+
# This could be a string, in which case it is available now.
|
48
|
+
# Otherwise it is an expression that takes the regexp from the input document,
|
49
|
+
# and the iregexp will not be available until interpretation.
|
50
|
+
parameters << parse_function_parameter
|
51
|
+
raise "expect group_end token, found #{current}" unless current.type == :group_end
|
52
|
+
|
53
|
+
AST::Function.new('search', parameters) do |str, str_iregexp|
|
54
|
+
if str.is_a?(String) && str_iregexp.is_a?(String)
|
55
|
+
regexp = translate_iregex_to_ruby_regex(str_iregexp, anchor: false)
|
56
|
+
regexp.match?(str)
|
57
|
+
else
|
58
|
+
false # result defined by RFC9535
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Janeway
|
4
|
+
module Functions
|
5
|
+
# 2.4.8. value() Function Extension
|
6
|
+
|
7
|
+
# Parameters:
|
8
|
+
# 1. NodesType
|
9
|
+
# Result:
|
10
|
+
# ValueType
|
11
|
+
#
|
12
|
+
# The value() function extension provides a way to convert an instance of
|
13
|
+
# NodesType to a value and make that available for further processing in
|
14
|
+
# the filter expression:
|
15
|
+
#
|
16
|
+
# @example $[?value(@..color) == "red"]
|
17
|
+
#
|
18
|
+
# Its only argument is an instance of NodesType (possibly taken from a
|
19
|
+
# filter-query, as in the example above). The result is an instance of
|
20
|
+
# ValueType.
|
21
|
+
#
|
22
|
+
# If the argument contains a single node, the result is the value of the node.
|
23
|
+
#
|
24
|
+
# If the argument is the empty nodelist or contains multiple nodes, the result is Nothing.
|
25
|
+
#
|
26
|
+
# Note: A singular query may be used anywhere where a ValueType is
|
27
|
+
# expected, so there is no need to use the value() function extension with a singular query.
|
28
|
+
def parse_function_value
|
29
|
+
consume # function
|
30
|
+
raise "expect group_start token, found #{current}" unless current.type == :group_start
|
31
|
+
|
32
|
+
consume # (
|
33
|
+
|
34
|
+
# Read parameter
|
35
|
+
parameters = [parse_function_parameter]
|
36
|
+
raise "expect group_end token, found #{current}" unless current.type == :group_end
|
37
|
+
|
38
|
+
AST::Function.new('value', parameters) do |nodes|
|
39
|
+
if nodes.is_a?(Array) && nodes.size == 1
|
40
|
+
nodes.first
|
41
|
+
else
|
42
|
+
:nothing
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'ast/function'
|
4
|
+
|
5
|
+
module Janeway
|
6
|
+
# Mixin to provide JSONPath function handlers for Parser
|
7
|
+
module Functions
|
8
|
+
# Convert IRegexp format to ruby regexp equivalent, following the instructions in rfc9485.
|
9
|
+
# @see https://www.rfc-editor.org/rfc/rfc9485.html#name-pcre-re2-and-ruby-regexps
|
10
|
+
# @param iregex [String]
|
11
|
+
# @param anchor [Boolean] add anchors to match the string start and end
|
12
|
+
# @return [Regexp]
|
13
|
+
def translate_iregex_to_ruby_regex(iregex, anchor: true)
|
14
|
+
# * For any unescaped dots (.) outside character classes (first
|
15
|
+
# alternative of charClass production), replace the dot with [^\n\r].
|
16
|
+
chars = iregex.chars
|
17
|
+
in_char_class = false
|
18
|
+
indexes = []
|
19
|
+
chars.each_with_index do |char, i|
|
20
|
+
# FIXME: does not handle escaped '[', ']', or '.'
|
21
|
+
case char
|
22
|
+
when '[' then in_char_class = true
|
23
|
+
when ']' then in_char_class = false
|
24
|
+
when '.'
|
25
|
+
next if in_char_class || chars[i-1] == '\\' # escaped dot
|
26
|
+
|
27
|
+
indexes << i # replace this dot
|
28
|
+
end
|
29
|
+
end
|
30
|
+
indexes.reverse_each do |i|
|
31
|
+
chars[i] = '[^\n\r]'
|
32
|
+
end
|
33
|
+
|
34
|
+
# * Enclose the regexp in \A(?: and )\z.
|
35
|
+
regex_str = anchor ? format('\A(?:%s)\z', chars.join) : chars.join
|
36
|
+
Regexp.new(regex_str)
|
37
|
+
end
|
38
|
+
|
39
|
+
# All jsonpath function parameters are one of these accepted types.
|
40
|
+
# Parse the function parameter and return the result.
|
41
|
+
# @return [String, AST::CurrentNode, AST::RootNode]
|
42
|
+
def parse_function_parameter
|
43
|
+
result =
|
44
|
+
case current.type
|
45
|
+
when :string then parse_string
|
46
|
+
when :current_node then parse_current_node
|
47
|
+
when :root then parse_root
|
48
|
+
else
|
49
|
+
# Invalid, no function uses this.
|
50
|
+
# Instead of crashing here, accept it and let the function return an empty result.
|
51
|
+
parse_expr
|
52
|
+
end
|
53
|
+
consume
|
54
|
+
result
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Require function definitions
|
60
|
+
Dir.children("#{__dir__}/functions/").each do |path|
|
61
|
+
require_relative "functions/#{path[0..-4]}"
|
62
|
+
end
|