json-ld 3.0.2 → 3.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 +4 -4
- data/AUTHORS +1 -1
- data/README.md +90 -53
- data/UNLICENSE +1 -1
- data/VERSION +1 -1
- data/bin/jsonld +4 -4
- data/lib/json/ld.rb +27 -10
- data/lib/json/ld/api.rb +325 -96
- data/lib/json/ld/compact.rb +75 -27
- data/lib/json/ld/conneg.rb +188 -0
- data/lib/json/ld/context.rb +677 -292
- data/lib/json/ld/expand.rb +240 -75
- data/lib/json/ld/flatten.rb +5 -3
- data/lib/json/ld/format.rb +19 -19
- data/lib/json/ld/frame.rb +135 -85
- data/lib/json/ld/from_rdf.rb +44 -17
- data/lib/json/ld/html/nokogiri.rb +151 -0
- data/lib/json/ld/html/rexml.rb +186 -0
- data/lib/json/ld/reader.rb +25 -5
- data/lib/json/ld/resource.rb +2 -2
- data/lib/json/ld/streaming_writer.rb +3 -1
- data/lib/json/ld/to_rdf.rb +47 -17
- data/lib/json/ld/utils.rb +4 -2
- data/lib/json/ld/writer.rb +75 -14
- data/spec/api_spec.rb +13 -34
- data/spec/compact_spec.rb +968 -9
- data/spec/conneg_spec.rb +373 -0
- data/spec/context_spec.rb +447 -53
- data/spec/expand_spec.rb +1872 -416
- data/spec/flatten_spec.rb +434 -47
- data/spec/frame_spec.rb +979 -344
- data/spec/from_rdf_spec.rb +305 -5
- data/spec/spec_helper.rb +177 -0
- data/spec/streaming_writer_spec.rb +4 -4
- data/spec/suite_compact_spec.rb +2 -2
- data/spec/suite_expand_spec.rb +14 -2
- data/spec/suite_flatten_spec.rb +10 -2
- data/spec/suite_frame_spec.rb +3 -2
- data/spec/suite_from_rdf_spec.rb +2 -2
- data/spec/suite_helper.rb +55 -20
- data/spec/suite_html_spec.rb +22 -0
- data/spec/suite_http_spec.rb +35 -0
- data/spec/suite_remote_doc_spec.rb +2 -2
- data/spec/suite_to_rdf_spec.rb +14 -3
- data/spec/support/extensions.rb +5 -1
- data/spec/test-files/test-4-input.json +3 -3
- data/spec/test-files/test-5-input.json +2 -2
- data/spec/test-files/test-8-framed.json +14 -18
- data/spec/to_rdf_spec.rb +606 -16
- data/spec/writer_spec.rb +5 -5
- metadata +144 -88
data/lib/json/ld/flatten.rb
CHANGED
@@ -18,14 +18,11 @@ module JSON::LD
|
|
18
18
|
# Property within current node
|
19
19
|
# @param [Array] list (nil)
|
20
20
|
# Used when property value is a list
|
21
|
-
# @param [Boolean] ordered (true)
|
22
|
-
# Ensure output objects have keys ordered properly
|
23
21
|
def create_node_map(element, graph_map,
|
24
22
|
active_graph: '@default',
|
25
23
|
active_subject: nil,
|
26
24
|
active_property: nil,
|
27
25
|
list: nil)
|
28
|
-
log_debug("node_map") {"active_graph: #{active_graph}, element: #{element.inspect}, active_subject: #{active_subject}"}
|
29
26
|
if element.is_a?(Array)
|
30
27
|
# If element is an array, process each entry in element recursively by passing item for element, node map, active graph, active subject, active property, and list.
|
31
28
|
element.map do |o|
|
@@ -112,6 +109,11 @@ module JSON::LD
|
|
112
109
|
active_graph: id)
|
113
110
|
end
|
114
111
|
|
112
|
+
if element['@included']
|
113
|
+
create_node_map(element.delete('@included'), graph_map,
|
114
|
+
active_graph: active_graph)
|
115
|
+
end
|
116
|
+
|
115
117
|
element.keys.each do |property|
|
116
118
|
value = element[property]
|
117
119
|
|
data/lib/json/ld/format.rb
CHANGED
@@ -18,8 +18,8 @@ module JSON::LD
|
|
18
18
|
# @example Obtaining serialization format file extension mappings
|
19
19
|
# RDF::Format.file_extensions #=> {:jsonld => [JSON::LD::Format] }
|
20
20
|
#
|
21
|
-
# @see
|
22
|
-
# @see https://json-ld
|
21
|
+
# @see https://www.w3.org/TR/json-ld11/
|
22
|
+
# @see https://w3c.github.io/json-ld-api/tests/
|
23
23
|
class Format < RDF::Format
|
24
24
|
content_type 'application/ld+json',
|
25
25
|
extension: :jsonld,
|
@@ -54,7 +54,7 @@ module JSON::LD
|
|
54
54
|
parse: false,
|
55
55
|
help: "expand [--context <context-file>] files ...",
|
56
56
|
filter: {output_format: :jsonld}, # Only shows output format set
|
57
|
-
lambda: ->(files, options) do
|
57
|
+
lambda: ->(files, **options) do
|
58
58
|
out = options[:output] || $stdout
|
59
59
|
out.set_encoding(Encoding::UTF_8) if RUBY_PLATFORM == "java"
|
60
60
|
options = options.merge(expandContext: options.delete(:context)) if options.has_key?(:context)
|
@@ -75,7 +75,7 @@ module JSON::LD
|
|
75
75
|
end
|
76
76
|
else
|
77
77
|
# Turn RDF into JSON-LD first
|
78
|
-
RDF::CLI.parse(files, options) do |reader|
|
78
|
+
RDF::CLI.parse(files, **options) do |reader|
|
79
79
|
JSON::LD::API.fromRdf(reader) do |expanded|
|
80
80
|
out.puts expanded.to_json(JSON::LD::JSON_STATE)
|
81
81
|
end
|
@@ -89,7 +89,7 @@ module JSON::LD
|
|
89
89
|
parse: false,
|
90
90
|
filter: {output_format: :jsonld}, # Only shows output format set
|
91
91
|
help: "compact --context <context-file> files ...",
|
92
|
-
lambda: ->(files, options) do
|
92
|
+
lambda: ->(files, **options) do
|
93
93
|
raise ArgumentError, "Compacting requires a context" unless options[:context]
|
94
94
|
out = options[:output] || $stdout
|
95
95
|
out.set_encoding(Encoding::UTF_8) if RUBY_PLATFORM == "java"
|
@@ -98,21 +98,21 @@ module JSON::LD
|
|
98
98
|
# If files are empty, either use options[:execute]
|
99
99
|
input = options[:evaluate] ? StringIO.new(options[:evaluate]) : STDIN
|
100
100
|
input.set_encoding(options.fetch(:encoding, Encoding::UTF_8))
|
101
|
-
JSON::LD::API.compact(input, options[:context], options) do |compacted|
|
101
|
+
JSON::LD::API.compact(input, options[:context], **options) do |compacted|
|
102
102
|
out.puts compacted.to_json(JSON::LD::JSON_STATE)
|
103
103
|
end
|
104
104
|
else
|
105
105
|
files.each do |file|
|
106
|
-
JSON::LD::API.compact(file, options[:context], options) do |compacted|
|
106
|
+
JSON::LD::API.compact(file, options[:context], **options) do |compacted|
|
107
107
|
out.puts compacted.to_json(JSON::LD::JSON_STATE)
|
108
108
|
end
|
109
109
|
end
|
110
110
|
end
|
111
111
|
else
|
112
112
|
# Turn RDF into JSON-LD first
|
113
|
-
RDF::CLI.parse(files, options) do |reader|
|
113
|
+
RDF::CLI.parse(files, **options) do |reader|
|
114
114
|
JSON::LD::API.fromRdf(reader) do |expanded|
|
115
|
-
JSON::LD::API.compact(expanded, options[:context], options) do |compacted|
|
115
|
+
JSON::LD::API.compact(expanded, options[:context], **options) do |compacted|
|
116
116
|
out.puts compacted.to_json(JSON::LD::JSON_STATE)
|
117
117
|
end
|
118
118
|
end
|
@@ -134,7 +134,7 @@ module JSON::LD
|
|
134
134
|
parse: false,
|
135
135
|
help: "flatten [--context <context-file>] files ...",
|
136
136
|
filter: {output_format: :jsonld}, # Only shows output format set
|
137
|
-
lambda: ->(files, options) do
|
137
|
+
lambda: ->(files, **options) do
|
138
138
|
out = options[:output] || $stdout
|
139
139
|
out.set_encoding(Encoding::UTF_8) if RUBY_PLATFORM == "java"
|
140
140
|
if options[:format] == :jsonld
|
@@ -142,21 +142,21 @@ module JSON::LD
|
|
142
142
|
# If files are empty, either use options[:execute]
|
143
143
|
input = options[:evaluate] ? StringIO.new(options[:evaluate]) : STDIN
|
144
144
|
input.set_encoding(options.fetch(:encoding, Encoding::UTF_8))
|
145
|
-
JSON::LD::API.flatten(input, options[:context], options) do |flattened|
|
145
|
+
JSON::LD::API.flatten(input, options[:context], **options) do |flattened|
|
146
146
|
out.puts flattened.to_json(JSON::LD::JSON_STATE)
|
147
147
|
end
|
148
148
|
else
|
149
149
|
files.each do |file|
|
150
|
-
JSON::LD::API.flatten(file, options[:context], options) do |flattened|
|
150
|
+
JSON::LD::API.flatten(file, options[:context], **options) do |flattened|
|
151
151
|
out.puts flattened.to_json(JSON::LD::JSON_STATE)
|
152
152
|
end
|
153
153
|
end
|
154
154
|
end
|
155
155
|
else
|
156
156
|
# Turn RDF into JSON-LD first
|
157
|
-
RDF::CLI.parse(files, options) do |reader|
|
157
|
+
RDF::CLI.parse(files, **options) do |reader|
|
158
158
|
JSON::LD::API.fromRdf(reader) do |expanded|
|
159
|
-
JSON::LD::API.flatten(expanded, options[:context], options) do |flattened|
|
159
|
+
JSON::LD::API.flatten(expanded, options[:context], **options) do |flattened|
|
160
160
|
out.puts flattened.to_json(JSON::LD::JSON_STATE)
|
161
161
|
end
|
162
162
|
end
|
@@ -169,7 +169,7 @@ module JSON::LD
|
|
169
169
|
parse: false,
|
170
170
|
help: "frame --frame <frame-file> files ...",
|
171
171
|
filter: {output_format: :jsonld}, # Only shows output format set
|
172
|
-
lambda: ->(files, options) do
|
172
|
+
lambda: ->(files, **options) do
|
173
173
|
raise ArgumentError, "Framing requires a frame" unless options[:frame]
|
174
174
|
out = options[:output] || $stdout
|
175
175
|
out.set_encoding(Encoding::UTF_8) if RUBY_PLATFORM == "java"
|
@@ -178,21 +178,21 @@ module JSON::LD
|
|
178
178
|
# If files are empty, either use options[:execute]
|
179
179
|
input = options[:evaluate] ? StringIO.new(options[:evaluate]) : STDIN
|
180
180
|
input.set_encoding(options.fetch(:encoding, Encoding::UTF_8))
|
181
|
-
JSON::LD::API.frame(input, options[:frame], options) do |framed|
|
181
|
+
JSON::LD::API.frame(input, options[:frame], **options) do |framed|
|
182
182
|
out.puts framed.to_json(JSON::LD::JSON_STATE)
|
183
183
|
end
|
184
184
|
else
|
185
185
|
files.each do |file|
|
186
|
-
JSON::LD::API.frame(file, options[:frame], options) do |framed|
|
186
|
+
JSON::LD::API.frame(file, options[:frame], **options) do |framed|
|
187
187
|
out.puts framed.to_json(JSON::LD::JSON_STATE)
|
188
188
|
end
|
189
189
|
end
|
190
190
|
end
|
191
191
|
else
|
192
192
|
# Turn RDF into JSON-LD first
|
193
|
-
RDF::CLI.parse(files, options) do |reader|
|
193
|
+
RDF::CLI.parse(files, **options) do |reader|
|
194
194
|
JSON::LD::API.fromRdf(reader) do |expanded|
|
195
|
-
JSON::LD::API.frame(expanded, options[:frame], options) do |framed|
|
195
|
+
JSON::LD::API.frame(expanded, options[:frame], **options) do |framed|
|
196
196
|
out.puts framed.to_json(JSON::LD::JSON_STATE)
|
197
197
|
end
|
198
198
|
end
|
data/lib/json/ld/frame.rb
CHANGED
@@ -23,11 +23,6 @@ module JSON::LD
|
|
23
23
|
# @param [Hash{Symbol => Object}] options ({})
|
24
24
|
# @raise [JSON::LD::InvalidFrame]
|
25
25
|
def frame(state, subjects, frame, parent: nil, property: nil, ordered: false, **options)
|
26
|
-
#log_depth do
|
27
|
-
#log_debug("frame") {"subjects: #{subjects.inspect}"}
|
28
|
-
#log_debug("frame") {"frame: #{frame.to_json(JSON_STATE)}"}
|
29
|
-
#log_debug("frame") {"property: #{property.inspect}"}
|
30
|
-
|
31
26
|
# Validate the frame
|
32
27
|
validate_frame(frame)
|
33
28
|
frame = frame.first if frame.is_a?(Array)
|
@@ -66,28 +61,43 @@ module JSON::LD
|
|
66
61
|
output = {'@id' => id}
|
67
62
|
link[id] = output
|
68
63
|
|
69
|
-
|
70
|
-
|
64
|
+
if %w(@first @last).include?(flags[:embed]) && context.processingMode('json-ld-1.1')
|
65
|
+
raise JSON::LD::JsonLdError::InvalidEmbedValue, "#{flags[:embed]} is not a valid value of @embed in 1.1 mode" if @options[:validate]
|
66
|
+
warn "[DEPRECATION] #{flags[:embed]} is not a valid value of @embed in 1.1 mode.\n"
|
67
|
+
end
|
68
|
+
|
69
|
+
if !state[:embedded] && state[:uniqueEmbeds][state[:graph]].has_key?(id)
|
70
|
+
# Skip adding this node object to the top-level, as it was included in another node object
|
71
|
+
next
|
72
|
+
elsif state[:embedded] &&
|
73
|
+
(flags[:embed] == '@never' || creates_circular_reference(subject, state[:graph], state[:subjectStack]))
|
74
|
+
# if embed is @never or if a circular reference would be created by an embed, the subject cannot be embedded, just add the reference; note that a circular reference won't occur when the embed flag is `@link` as the above check will short-circuit before reaching this point
|
71
75
|
add_frame_output(parent, property, output)
|
72
76
|
next
|
73
|
-
|
77
|
+
elsif state[:embedded] &&
|
78
|
+
%w(@first @once).include?(flags[:embed]) &&
|
79
|
+
state[:uniqueEmbeds][state[:graph]].has_key?(id)
|
74
80
|
|
75
|
-
|
76
|
-
|
81
|
+
# if only the first match should be embedded
|
82
|
+
# Embed unless already embedded
|
83
|
+
add_frame_output(parent, property, output)
|
84
|
+
next
|
85
|
+
elsif flags[:embed] == '@last'
|
86
|
+
# if only the last match should be embedded
|
77
87
|
# remove any existing embed
|
78
88
|
remove_embed(state, id) if state[:uniqueEmbeds][state[:graph]].include?(id)
|
79
|
-
state[:uniqueEmbeds][state[:graph]][id] = {
|
80
|
-
parent: parent,
|
81
|
-
property: property
|
82
|
-
}
|
83
89
|
end
|
84
90
|
|
91
|
+
state[:uniqueEmbeds][state[:graph]][id] = {
|
92
|
+
parent: parent,
|
93
|
+
property: property
|
94
|
+
}
|
95
|
+
|
85
96
|
# push matching subject onto stack to enable circular embed checks
|
86
97
|
state[:subjectStack] << {subject: subject, graph: state[:graph]}
|
87
98
|
|
88
99
|
# Subject is also the name of a graph
|
89
100
|
if state[:graphMap].has_key?(id)
|
90
|
-
log_debug("frame") {"#{id} in graphMap"}
|
91
101
|
# check frame's "@graph" to see what to do next
|
92
102
|
# 1. if it doesn't exist and state.graph === "@merged", don't recurse
|
93
103
|
# 2. if it doesn't exist and state.graph !== "@merged", recurse
|
@@ -105,12 +115,15 @@ module JSON::LD
|
|
105
115
|
|
106
116
|
if recurse
|
107
117
|
state[:graphStack].push(state[:graph])
|
108
|
-
state[:
|
109
|
-
frame(state, state[:graphMap][id].keys, [subframe], parent: output, property: '@graph', **options)
|
110
|
-
state[:graph] = state[:graphStack].pop
|
118
|
+
frame(state.merge(graph: id, embedded: false), state[:graphMap][id].keys, [subframe], parent: output, property: '@graph', **options)
|
111
119
|
end
|
112
120
|
end
|
113
121
|
|
122
|
+
# If frame has `@included`, recurse over it's sub-frame
|
123
|
+
if frame['@included']
|
124
|
+
frame(state.merge(embedded: false), subjects, frame['@included'], parent: output, property: '@included', **options)
|
125
|
+
end
|
126
|
+
|
114
127
|
# iterate over subject properties in order
|
115
128
|
subject.keys.opt_sort(ordered: ordered).each do |prop|
|
116
129
|
objects = subject[prop]
|
@@ -139,14 +152,14 @@ module JSON::LD
|
|
139
152
|
src = o['@list']
|
140
153
|
src.each do |oo|
|
141
154
|
if node_reference?(oo)
|
142
|
-
frame(state, [oo['@id']], subframe, parent: list, property: '@list', **options)
|
155
|
+
frame(state.merge(embedded: true), [oo['@id']], subframe, parent: list, property: '@list', **options)
|
143
156
|
else
|
144
157
|
add_frame_output(list, '@list', oo.dup)
|
145
158
|
end
|
146
159
|
end
|
147
160
|
when node_reference?(o)
|
148
161
|
# recurse into subject reference
|
149
|
-
frame(state, [o['@id']], subframe, parent: output, property: prop, **options)
|
162
|
+
frame(state.merge(embedded: true), [o['@id']], subframe, parent: output, property: prop, **options)
|
150
163
|
when value_match?(subframe, o)
|
151
164
|
# Include values if they match
|
152
165
|
add_frame_output(output, prop, o.dup)
|
@@ -156,7 +169,11 @@ module JSON::LD
|
|
156
169
|
|
157
170
|
# handle defaults in order
|
158
171
|
frame.keys.opt_sort(ordered: ordered).each do |prop|
|
159
|
-
|
172
|
+
if prop == '@type' && frame[prop].first.is_a?(Hash) && frame[prop].first.keys == %w(@default)
|
173
|
+
# Treat this as a default
|
174
|
+
elsif prop.start_with?('@')
|
175
|
+
next
|
176
|
+
end
|
160
177
|
|
161
178
|
# if omit default is off, then include default values for properties that appear in the next frame but are not in the matching subject
|
162
179
|
n = frame[prop].first || {}
|
@@ -174,7 +191,7 @@ module JSON::LD
|
|
174
191
|
# Node has property referencing this subject
|
175
192
|
# recurse into reference
|
176
193
|
(output['@reverse'] ||= {})[reverse_prop] ||= []
|
177
|
-
frame(state, [r_id], subframe, parent: output['@reverse'][reverse_prop], property: property, **options)
|
194
|
+
frame(state.merge(embedded: true), [r_id], subframe, parent: output['@reverse'][reverse_prop], property: property, **options)
|
178
195
|
end
|
179
196
|
end
|
180
197
|
end
|
@@ -243,35 +260,43 @@ module JSON::LD
|
|
243
260
|
##
|
244
261
|
# Replace @preserve keys with the values, also replace @null with null.
|
245
262
|
#
|
246
|
-
# Optionally, remove BNode identifiers only used once.
|
247
|
-
#
|
248
263
|
# @param [Array, Hash] input
|
249
264
|
# @return [Array, Hash]
|
250
265
|
def cleanup_preserve(input)
|
251
|
-
|
266
|
+
case input
|
252
267
|
when Array
|
253
268
|
# If, after replacement, an array contains only the value null remove the value, leaving an empty array.
|
254
|
-
|
255
|
-
|
256
|
-
# If the array contains a single member, which is itself an array, use that value as the result
|
257
|
-
(v.length == 1 && v.first.is_a?(Array)) ? v.first : v
|
269
|
+
input.map {|o| cleanup_preserve(o)}
|
258
270
|
when Hash
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
v = cleanup_preserve(value)
|
266
|
-
|
267
|
-
# Because we may have added a null value to an array, we need to clean that up, if we possible
|
268
|
-
v = v.first if v.is_a?(Array) && v.length == 1 && !context.as_array?(key)
|
269
|
-
output[key] = v
|
271
|
+
if input.has_key?('@preserve')
|
272
|
+
# Replace with the content of `@preserve`
|
273
|
+
cleanup_preserve(input['@preserve'].first)
|
274
|
+
else
|
275
|
+
input.inject({}) do |memo, (k,v)|
|
276
|
+
memo.merge(k => cleanup_preserve(v))
|
270
277
|
end
|
271
278
|
end
|
272
|
-
|
279
|
+
else
|
280
|
+
input
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
##
|
285
|
+
# Replace `@null` with `null`, removing it from arrays.
|
286
|
+
#
|
287
|
+
# @param [Array, Hash] input
|
288
|
+
# @return [Array, Hash]
|
289
|
+
def cleanup_null(input)
|
290
|
+
result = case input
|
291
|
+
when Array
|
292
|
+
# If, after replacement, an array contains only the value null remove the value, leaving an empty array.
|
293
|
+
input.map {|o| cleanup_null(o)}.compact
|
294
|
+
when Hash
|
295
|
+
input.inject({}) do |memo, (k,v)|
|
296
|
+
memo.merge(k => cleanup_null(v))
|
297
|
+
end
|
273
298
|
when '@null'
|
274
|
-
# If the value from the key-pair is @null, replace the value with
|
299
|
+
# If the value from the key-pair is @null, replace the value with null
|
275
300
|
nil
|
276
301
|
else
|
277
302
|
input
|
@@ -304,7 +329,7 @@ module JSON::LD
|
|
304
329
|
#
|
305
330
|
# Matches either based on explicit type inclusion where the node has any type listed in the frame. If the frame has empty types defined matches nodes not having a @type. If the frame has a type of {} defined matches nodes having any type defined.
|
306
331
|
#
|
307
|
-
# Otherwise, does duck typing, where the node must have all of the properties defined in the frame.
|
332
|
+
# Otherwise, does duck typing, where the node must have any or all of the properties defined in the frame, depending on the `requireAll` flag.
|
308
333
|
#
|
309
334
|
# @param [Hash{String => Object}] subject the subject to check.
|
310
335
|
# @param [Hash{String => Object}] frame the frame to check.
|
@@ -324,24 +349,39 @@ module JSON::LD
|
|
324
349
|
ids = v || []
|
325
350
|
|
326
351
|
# Match on specific @id.
|
327
|
-
|
328
|
-
|
352
|
+
match_this = case ids
|
353
|
+
when [], [{}]
|
354
|
+
# Match on no @id or any @id
|
355
|
+
true
|
356
|
+
else
|
357
|
+
# Match on specific @id
|
358
|
+
ids.include?(subject['@id'])
|
359
|
+
end
|
360
|
+
return match_this if !flags[:requireAll]
|
329
361
|
when '@type'
|
330
362
|
# No longer a wildcard pattern
|
331
363
|
wildcard = false
|
332
|
-
|
364
|
+
|
333
365
|
match_this = case v
|
334
366
|
when []
|
335
|
-
# Don't
|
367
|
+
# Don't match with any @type
|
336
368
|
return false if !node_values.empty?
|
337
369
|
true
|
338
370
|
when [{}]
|
339
|
-
# Match
|
371
|
+
# Match with any @type
|
340
372
|
!node_values.empty?
|
341
373
|
else
|
342
|
-
#
|
343
|
-
|
374
|
+
# Treat a map with @default like an empty map
|
375
|
+
if v.first.is_a?(Hash) && v.first.keys == %w(@default)
|
376
|
+
true
|
377
|
+
elsif (v & node_values).empty?
|
378
|
+
# Match on specific @type
|
379
|
+
false
|
380
|
+
else
|
381
|
+
true
|
382
|
+
end
|
344
383
|
end
|
384
|
+
return match_this if !flags[:requireAll]
|
345
385
|
when /@/
|
346
386
|
# Skip other keywords
|
347
387
|
next
|
@@ -352,7 +392,6 @@ module JSON::LD
|
|
352
392
|
has_default = v.has_key?('@default')
|
353
393
|
end
|
354
394
|
|
355
|
-
|
356
395
|
# No longer a wildcard pattern if frame has any non-keyword properties
|
357
396
|
wildcard = false
|
358
397
|
|
@@ -362,42 +401,40 @@ module JSON::LD
|
|
362
401
|
# If frame value is empty, don't match if subject has any value
|
363
402
|
return false if !node_values.empty? && is_empty
|
364
403
|
|
365
|
-
match_this = case
|
366
|
-
when nil
|
404
|
+
match_this = case
|
405
|
+
when v.nil?
|
367
406
|
# node does not match if values is not empty and the value of property in frame is match none.
|
368
407
|
return false unless node_values.empty?
|
369
408
|
true
|
370
|
-
when Hash
|
371
|
-
# node matches if values is not empty and the value of property in frame is wildcard
|
409
|
+
when v.is_a?(Hash) && (v.keys - FRAMING_KEYWORDS).empty?
|
410
|
+
# node matches if values is not empty and the value of property in frame is wildcard (frame with properties other than framing keywords)
|
372
411
|
!node_values.empty?
|
373
|
-
|
374
|
-
|
412
|
+
when value?(v)
|
413
|
+
# Match on any matching value
|
414
|
+
node_values.any? {|nv| value_match?(v, nv)}
|
415
|
+
when node?(v) || node_reference?(v)
|
416
|
+
node_values.any? do |nv|
|
417
|
+
node_match?(v, nv, state, flags)
|
418
|
+
end
|
419
|
+
when list?(v)
|
420
|
+
vv = v['@list'].first
|
421
|
+
node_values = list?(node_values.first) ?
|
422
|
+
node_values.first['@list'] :
|
423
|
+
false
|
424
|
+
if !node_values
|
425
|
+
false # Lists match Lists
|
426
|
+
elsif value?(vv)
|
375
427
|
# Match on any matching value
|
376
|
-
node_values.any? {|nv| value_match?(
|
377
|
-
elsif node?(
|
428
|
+
node_values.any? {|nv| value_match?(vv, nv)}
|
429
|
+
elsif node?(vv) || node_reference?(vv)
|
378
430
|
node_values.any? do |nv|
|
379
|
-
node_match?(
|
380
|
-
end
|
381
|
-
elsif list?(v)
|
382
|
-
vv = v['@list'].first
|
383
|
-
node_values = list?(node_values.first) ?
|
384
|
-
node_values.first['@list'] :
|
385
|
-
false
|
386
|
-
if !node_values
|
387
|
-
false # Lists match Lists
|
388
|
-
elsif value?(vv)
|
389
|
-
# Match on any matching value
|
390
|
-
node_values.any? {|nv| value_match?(vv, nv)}
|
391
|
-
elsif node?(vv) || node_reference?(vv)
|
392
|
-
node_values.any? do |nv|
|
393
|
-
node_match?(vv, nv, state, flags)
|
394
|
-
end
|
395
|
-
else
|
396
|
-
false
|
431
|
+
node_match?(vv, nv, state, flags)
|
397
432
|
end
|
398
433
|
else
|
399
|
-
false
|
434
|
+
false
|
400
435
|
end
|
436
|
+
else
|
437
|
+
false # No matching on non-value or node values
|
401
438
|
end
|
402
439
|
end
|
403
440
|
|
@@ -412,9 +449,18 @@ module JSON::LD
|
|
412
449
|
end
|
413
450
|
|
414
451
|
def validate_frame(frame)
|
415
|
-
raise InvalidFrame
|
416
|
-
"Invalid JSON-LD syntax; a JSON-LD frame must be an object: #{frame.inspect}" unless
|
452
|
+
raise JsonLdError::InvalidFrame,
|
453
|
+
"Invalid JSON-LD frame syntax; a JSON-LD frame must be an object: #{frame.inspect}" unless
|
417
454
|
frame.is_a?(Hash) || (frame.is_a?(Array) && frame.first.is_a?(Hash) && frame.length == 1)
|
455
|
+
frame = frame.first if frame.is_a?(Array)
|
456
|
+
|
457
|
+
# Check values of @id and @type
|
458
|
+
raise JsonLdError::InvalidFrame,
|
459
|
+
"Invalid JSON-LD frame syntax; invalid value of @id: #{frame['@id']}" unless
|
460
|
+
Array(frame['@id']) == [{}] || Array(frame['@id']).all?{|v| RDF::URI(v).valid?}
|
461
|
+
raise JsonLdError::InvalidFrame,
|
462
|
+
"Invalid JSON-LD frame syntax; invalid value of @type: #{frame['@type']}" unless
|
463
|
+
Array(frame['@type']).all?{|v| v.is_a?(Hash) && (v.keys - %w(@default)).empty? || RDF::URI(v).valid?}
|
418
464
|
end
|
419
465
|
|
420
466
|
# Checks the current subject stack to see if embedding the given subject would cause a circular reference.
|
@@ -443,10 +489,12 @@ module JSON::LD
|
|
443
489
|
rval = rval.values.first if value?(rval)
|
444
490
|
if name == :embed
|
445
491
|
rval = case rval
|
446
|
-
when true then '@
|
492
|
+
when true then '@once'
|
447
493
|
when false then '@never'
|
448
|
-
when '@always', '@
|
449
|
-
else
|
494
|
+
when '@always', '@first', '@last', '@link', '@once', '@never' then rval
|
495
|
+
else
|
496
|
+
raise JsonLdError::InvalidEmbedValue,
|
497
|
+
"Invalid JSON-LD frame syntax; invalid value of @embed: #{rval}"
|
450
498
|
end
|
451
499
|
end
|
452
500
|
rval
|
@@ -542,12 +590,14 @@ module JSON::LD
|
|
542
590
|
# * @languages are the same or `value[@language]` is not null and `pattern[@language]` is `{}`, or `value[@language]` is null and `pattern[@language]` is null or `[]`.
|
543
591
|
def value_match?(pattern, value)
|
544
592
|
v1, t1, l1 = value['@value'], value['@type'], value['@language']
|
545
|
-
v2, t2, l2 = Array(pattern['@value']), Array(pattern['@type']), Array(pattern['@language'])
|
593
|
+
v2, t2, l2 = Array(pattern['@value']), Array(pattern['@type']), Array(pattern['@language']).map {|v| v.is_a?(String) ? v.downcase : v}
|
546
594
|
return true if (v2 + t2 + l2).empty?
|
547
595
|
return false unless v2.include?(v1) || v2 == [{}]
|
548
596
|
return false unless t2.include?(t1) || t1 && t2 == [{}] || t1.nil? && (t2 || []).empty?
|
549
|
-
return false unless l2.include?(l1) || l1 && l2 == [{}] || l1.nil? && (l2 || []).empty?
|
597
|
+
return false unless l2.include?(l1.to_s.downcase) || l1 && l2 == [{}] || l1.nil? && (l2 || []).empty?
|
550
598
|
true
|
551
599
|
end
|
600
|
+
|
601
|
+
FRAMING_KEYWORDS = %w(@default @embed @explicit @omitDefault @requireAll).freeze
|
552
602
|
end
|
553
603
|
end
|