json-ld 3.0.2 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|