json-ld 2.1.0 → 2.1.1

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
  SHA1:
3
- metadata.gz: 693e380cfc75c04525a1144d742a4c98737df5bf
4
- data.tar.gz: a6d0d85e8323532379bfeef2be95361b1a5c1a01
3
+ metadata.gz: cb9297e31b76d1e901ae278f349ed412086831f5
4
+ data.tar.gz: e07e7aeb8bb57349e5f819da903fb55524c46b43
5
5
  SHA512:
6
- metadata.gz: d18d9ad16dc730a96037c4b5f8b1926910d75820a94529a1287fe82dcc1e251b1c73fce936ba3988c6672f5615e8361298971018d24525ce51dd1c2667062492
7
- data.tar.gz: 34cfa1b4561de877b8eb94513a42c8c383f8ce037a03c67f9535cbd1a71ed4a9023e71a70a7ba8bb79b73c7c9e49827d664028af416a7f1b489f25a3dd821e8d
6
+ metadata.gz: 7baea9184fa7cea017a37cb7ec2aa2a4a4b18e153b6a771a8b5297bfe518ca669a66e9a84dc5e37dd04e1b135653ff9195f4b6fb56d48af1a835f1290f8862dd
7
+ data.tar.gz: fba430dad035dd6465e07ee94c952bf79c33be220baa642a57e07c8d21cd7aa3431e824c5487c3f3b43f4a23f7b30f55c62d059e4b6fb5c2ff1ac8828932b86e
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.1.0
1
+ 2.1.1
@@ -47,7 +47,6 @@ module JSON
47
47
  @context
48
48
  @default
49
49
  @embed
50
- @embedChildren
51
50
  @explicit
52
51
  @id
53
52
  @index
@@ -55,6 +54,7 @@ module JSON
55
54
  @language
56
55
  @list
57
56
  @omitDefault
57
+ @requireAll
58
58
  @reverse
59
59
  @set
60
60
  @type
@@ -70,8 +70,10 @@ module JSON::LD
70
70
  # A context that is used to initialize the active context when expanding a document.
71
71
  # @option options [Boolean, String, RDF::URI] :flatten
72
72
  # If set to a value that is not `false`, the JSON-LD processor must modify the output of the Compaction Algorithm or the Expansion Algorithm by coalescing all properties associated with each subject via the Flattening Algorithm. The value of `flatten must` be either an _IRI_ value representing the name of the graph to flatten, or `true`. If the value is `true`, then the first graph encountered in the input document is selected and flattened.
73
- # @option options [String] :processingMode ("json-ld-1.0")
74
- # If set to "json-ld-1.0", the JSON-LD processor must produce exactly the same results as the algorithms defined in this specification. If set to another value, the JSON-LD processor is allowed to extend or modify the algorithms defined in this specification to enable application-specific optimizations. The definition of such optimizations is beyond the scope of this specification and thus not defined. Consequently, different implementations may implement different optimizations. Developers must not define modes beginning with json-ld as they are reserved for future versions of this specification.
73
+ # @option options [String] :processingMode ("json-ld-1.1")
74
+ # Processing mode, json-ld-1.0 or json-ld-1.1. Also can have other values:
75
+ #
76
+ # * json-ld-1.1-expand-frame – special frame expansion mode.
75
77
  # @option options [Boolean] :rename_bnodes (true)
76
78
  # Rename bnodes as part of expansion, or keep them the same.
77
79
  # @option options [Boolean] :unique_bnodes (false)
@@ -84,9 +86,14 @@ module JSON::LD
84
86
  # @yieldparam [API]
85
87
  # @raise [JsonLdError]
86
88
  def initialize(input, context, options = {}, &block)
87
- @options = {compactArrays: true, rename_bnodes: true}.merge!(options)
88
- @options[:validate] = true if @options[:processingMode] == "json-ld-1.0"
89
- @options[:documentLoader] ||= self.class.method(:documentLoader)
89
+ @options = {
90
+ compactArrays: true,
91
+ rename_bnodes: true,
92
+ processingMode: "json-ld-1.1",
93
+ documentLoader: self.class.method(:documentLoader)
94
+ }
95
+ @options[:validate] = %w(json-ld-1.0 json-ld-1.1).include?(@options[:processingMode])
96
+ @options = @options.merge(options)
90
97
  @namer = options[:unique_bnodes] ? BlankNodeUniqer.new : (@options[:rename_bnodes] ? BlankNodeNamer.new("b") : BlankNodeMapper.new)
91
98
 
92
99
  # For context via Link header
@@ -127,8 +134,7 @@ module JSON::LD
127
134
 
128
135
  # If not provided, first use context from document, or from a Link header
129
136
  context ||= (@value['@context'] if @value.is_a?(Hash)) || context_ref
130
- @context = Context.new(@options)
131
- @context = @context.parse(context) if context
137
+ @context = Context.parse(context || {}, @options)
132
138
 
133
139
  if block_given?
134
140
  case block.arity
@@ -150,9 +156,7 @@ module JSON::LD
150
156
  # @param [String, #read, Hash, Array] input
151
157
  # The JSON-LD object to copy and perform the expansion upon.
152
158
  # @param [Hash{Symbol => Object}] options
153
- # See options in {JSON::LD::API#initialize}
154
- # @option options [String, #read, Hash, Array, JSON::LD::Context] :expandContext
155
- # A context that is used to initialize the active context when expanding a document.
159
+ # @option options (see #initialize)
156
160
  # @raise [JsonLdError]
157
161
  # @yield jsonld
158
162
  # @yieldparam [Array<Hash>] jsonld
@@ -164,10 +168,7 @@ module JSON::LD
164
168
  def self.expand(input, options = {})
165
169
  result = nil
166
170
  API.new(input, options[:expandContext], options) do |api|
167
- result = api.expand(api.value, nil, api.context,
168
- ordered: options.fetch(:ordered, true),
169
- framing: options.fetch(:framing, false),
170
- keep_free_floating_nodes: options.fetch(:keep_free_floating_nodes, false))
171
+ result = api.expand(api.value, nil, api.context, ordered: options.fetch(:ordered, true))
171
172
  end
172
173
 
173
174
  # If, after the algorithm outlined above is run, the resulting element is an
@@ -192,8 +193,7 @@ module JSON::LD
192
193
  # @param [String, #read, Hash, Array, JSON::LD::Context] context
193
194
  # The base context to use when compacting the input.
194
195
  # @param [Hash{Symbol => Object}] options
195
- # See options in {JSON::LD::API#initialize}
196
- # Other options passed to {JSON::LD::API.expand}
196
+ # @option options (see #initialize)
197
197
  # @option options [Boolean] :expanded Input is already expanded
198
198
  # @yield jsonld
199
199
  # @yieldparam [Hash] jsonld
@@ -212,7 +212,7 @@ module JSON::LD
212
212
 
213
213
  API.new(expanded_input, context, options) do
214
214
  log_debug(".compact") {"expanded input: #{expanded.to_json(JSON_STATE) rescue 'malformed json'}"}
215
- result = compact(value, nil)
215
+ result = compact(value)
216
216
 
217
217
  # xxx) Add the given context to the output
218
218
  ctx = self.context.serialize
@@ -235,8 +235,7 @@ module JSON::LD
235
235
  # @param [String, #read, Hash, Array, JSON::LD::EvaluationContext] context
236
236
  # An optional external context to use additionally to the context embedded in input when expanding the input.
237
237
  # @param [Hash{Symbol => Object}] options
238
- # See options in {JSON::LD::API#initialize}
239
- # Other options passed to {JSON::LD::API.expand}
238
+ # @option options (see #initialize)
240
239
  # @option options [Boolean] :expanded Input is already expanded
241
240
  # @yield jsonld
242
241
  # @yieldparam [Hash] jsonld
@@ -256,12 +255,12 @@ module JSON::LD
256
255
  log_debug(".flatten") {"expanded input: #{value.to_json(JSON_STATE) rescue 'malformed json'}"}
257
256
 
258
257
  # Initialize node map to a JSON object consisting of a single member whose key is @default and whose value is an empty JSON object.
259
- graphs = {'@default' => {}}
260
- create_node_map(value, graphs)
258
+ graph_maps = {'@default' => {}}
259
+ create_node_map(value, graph_maps)
261
260
 
262
- default_graph = graphs['@default']
263
- graphs.keys.kw_sort.reject {|k| k == '@default'}.each do |graph_name|
264
- graph = graphs[graph_name]
261
+ default_graph = graph_maps['@default']
262
+ graph_maps.keys.kw_sort.reject {|k| k == '@default'}.each do |graph_name|
263
+ graph = graph_maps[graph_name]
265
264
  entry = default_graph[graph_name] ||= {'@id' => graph_name}
266
265
  nodes = entry['@graph'] ||= []
267
266
  graph.keys.kw_sort.each do |id|
@@ -274,7 +273,7 @@ module JSON::LD
274
273
 
275
274
  if context && !flattened.empty?
276
275
  # Otherwise, return the result of compacting flattened according the Compaction algorithm passing context ensuring that the compaction result uses the @graph keyword (or its alias) at the top-level, even if the context is empty or if there is only one element to put in the @graph array. This ensures that the returned document has a deterministic structure.
277
- compacted = compact(flattened, nil)
276
+ compacted = compact(flattened)
278
277
  compacted = [compacted] unless compacted.is_a?(Array)
279
278
  kwgraph = self.context.compact_iri('@graph', quiet: true)
280
279
  flattened = self.context.serialize.merge(kwgraph => compacted)
@@ -295,9 +294,7 @@ module JSON::LD
295
294
  # The JSON-LD object to copy and perform the framing on.
296
295
  # @param [String, #read, Hash, Array] frame
297
296
  # The frame to use when re-arranging the data.
298
- # @param [Hash{Symbol => Object}] options
299
- # See options in {JSON::LD::API#initialize}
300
- # Other options passed to {JSON::LD::API.expand}
297
+ # @option options (see #initialize)
301
298
  # @option options ['@last', '@always', '@never', '@link'] :embed ('@last')
302
299
  # a flag specifying that objects should be directly embedded in the output,
303
300
  # instead of being referred to by their IRI.
@@ -305,6 +302,9 @@ module JSON::LD
305
302
  # a flag specifying that for properties to be included in the output,
306
303
  # they must be explicitly declared in the framing context.
307
304
  # @option options [Boolean] :requireAll (true)
305
+ # A flag specifying that all properties present in the input frame must
306
+ # either have a default value or be present in the JSON-LD input for the
307
+ # frame to match.
308
308
  # @option options [Boolean] :omitDefault (false)
309
309
  # a flag specifying that properties that are missing from the JSON-LD
310
310
  # input should be omitted from the output.
@@ -330,7 +330,8 @@ module JSON::LD
330
330
  }.merge!(options)
331
331
 
332
332
  framing_state = {
333
- graphs: {'@default' => {}, '@merged' => {}},
333
+ graphMap: {},
334
+ graphStack: [],
334
335
  subjectStack: [],
335
336
  link: {},
336
337
  }
@@ -351,17 +352,27 @@ module JSON::LD
351
352
  expanded_input = options[:expanded] ? input : API.expand(input, options)
352
353
 
353
354
  # Expand frame to simplify processing
354
- expanded_frame = API.expand(frame, options.merge(framing: true, keep_free_floating_nodes: true))
355
+ expanded_frame = API.expand(frame, options.merge(processingMode: "json-ld-1.1-expand-frame"))
355
356
 
356
357
  # Initialize input using frame as context
357
358
  API.new(expanded_input, nil, options) do
358
359
  log_debug(".frame") {"expanded frame: #{expanded_frame.to_json(JSON_STATE) rescue 'malformed json'}"}
359
360
 
360
361
  # Get framing nodes from expanded input, replacing Blank Node identifiers as necessary
361
- old_logger, @options[:logger] = @options[:logger], []
362
- create_node_map(value, framing_state[:graphs], graph: '@merged')
363
- @options[:logger] = old_logger
364
- framing_state[:subjects] = framing_state[:graphs]['@merged']
362
+ create_node_map(value, framing_state[:graphMap], graph: '@default')
363
+
364
+ frame_keys = frame.keys.map {|k| context.expand_iri(k, vocab: true, quiet: true)}
365
+ if frame_keys.include?('@graph')
366
+ # If frame contains @graph, it matches the default graph.
367
+ framing_state[:graph] = '@default'
368
+ else
369
+ # If frame does not contain @graph used the merged graph.
370
+ framing_state[:graph] = '@merged'
371
+ framing_state[:link]['@merged'] = {}
372
+ framing_state[:graphMap]['@merged'] = merge_node_map_graphs(framing_state[:graphMap])
373
+ end
374
+
375
+ framing_state[:subjects] = framing_state[:graphMap][framing_state[:graph]]
365
376
 
366
377
  result = []
367
378
  frame(framing_state, framing_state[:subjects].keys.sort, (expanded_frame.first || {}), options.merge(parent: result))
@@ -369,7 +380,7 @@ module JSON::LD
369
380
  # Initalize context from frame
370
381
  @context = @context.parse(frame['@context'])
371
382
  # Compact result
372
- compacted = compact(result, nil)
383
+ compacted = compact(result)
373
384
  compacted = [compacted] unless compacted.is_a?(Array)
374
385
 
375
386
  # Add the given context to the output
@@ -387,9 +398,7 @@ module JSON::LD
387
398
  #
388
399
  # @param [String, #read, Hash, Array] input
389
400
  # The JSON-LD object to process when outputting statements.
390
- # @param [{Symbol,String => Object}] options
391
- # See options in {JSON::LD::API#initialize}
392
- # Options passed to {JSON::LD::API.expand}
401
+ # @option options (see #initialize)
393
402
  # @option options [Boolean] :produceGeneralizedRdf (false)
394
403
  # If true, output will include statements having blank node predicates, otherwise they are dropped.
395
404
  # @option options [Boolean] :expanded Input is already expanded
@@ -449,7 +458,7 @@ module JSON::LD
449
458
  #
450
459
  # @param [Array<RDF::Statement>] input
451
460
  # @param [Hash{Symbol => Object}] options
452
- # See options in {JSON::LD::API#initialize}
461
+ # @option options (see #initialize)
453
462
  # @option options [Boolean] :useRdfType (false)
454
463
  # If set to `true`, the JSON-LD processor will treat `rdf:type` like a normal property instead of using `@type`.
455
464
  # @yield jsonld
@@ -488,7 +497,7 @@ module JSON::LD
488
497
  # If the passed input is a DOMString representing the IRI of a remote document, dereference it. If the retrieved document's content type is neither application/json, nor application/ld+json, nor any other media type using a +json suffix as defined in [RFC6839], reject the promise passing an loading document failed error.
489
498
  if content_type && options[:validate]
490
499
  main, sub = content_type.split("/")
491
- raise JSON::LD::JsonLdError::LoadingDocumentFailed, "content_type: #{content_type}" if
500
+ raise JSON::LD::JsonLdError::LoadingDocumentFailed, "url: #{url}, content_type: #{content_type}" if
492
501
  main != 'application' ||
493
502
  sub !~ /^(.*\+)?json$/
494
503
  end
@@ -523,10 +532,6 @@ module JSON::LD
523
532
  def validate_input(input)
524
533
  return unless defined?(JsonLint)
525
534
  jsonlint = JsonLint::Linter.new
526
- unless jsonlint.respond_to?(:check_stream)
527
- warn "Skipping jsonlint, use latest version from GiHub until 0.1.1 released"
528
- return
529
- end
530
535
  input = StringIO.new(input) unless input.respond_to?(:read)
531
536
  unless jsonlint.check_stream(input)
532
537
  raise JsonLdError::LoadingDocumentFailed, jsonlint.errors[''].join("\n")
@@ -10,7 +10,7 @@ module JSON::LD
10
10
  # @param [Array, Hash] element
11
11
  # @param [String] property (nil)
12
12
  # @return [Array, Hash]
13
- def compact(element, property = nil)
13
+ def compact(element, property: nil)
14
14
  #if property.nil?
15
15
  # log_debug("compact") {"element: #{element.inspect}, ec: #{context.inspect}"}
16
16
  #else
@@ -19,7 +19,7 @@ module JSON::LD
19
19
  case element
20
20
  when Array
21
21
  #log_debug("") {"Array #{element.inspect}"}
22
- result = element.map {|item| compact(item, property)}.compact
22
+ result = element.map {|item| compact(item, property: property)}.compact
23
23
 
24
24
  # If element has a single member and the active property has no
25
25
  # @container mapping to @list or @set, the compacted value is that
@@ -65,7 +65,7 @@ module JSON::LD
65
65
  end
66
66
 
67
67
  if expanded_property == '@reverse'
68
- compacted_value = compact(expanded_value, '@reverse')
68
+ compacted_value = compact(expanded_value, property: '@reverse')
69
69
  #log_debug("@reverse") {"compacted_value: #{compacted_value.inspect}"}
70
70
  compacted_value.each do |prop, value|
71
71
  if context.reverse?(prop)
@@ -121,7 +121,7 @@ module JSON::LD
121
121
 
122
122
  container = context.container(item_active_property)
123
123
  value = list?(expanded_item) ? expanded_item['@list'] : expanded_item
124
- compacted_item = compact(value, item_active_property)
124
+ compacted_item = compact(value, property: item_active_property)
125
125
  #log_debug("") {" => compacted key: #{item_active_property.inspect} for #{compacted_item.inspect}"}
126
126
 
127
127
  if list?(expanded_item)
@@ -186,6 +186,19 @@ module JSON::LD
186
186
  # @return [BlankNodeNamer]
187
187
  attr_accessor :namer
188
188
 
189
+ ##
190
+ # Create a new context by parsing a context.
191
+ #
192
+ # @see #initialize
193
+ # @see #parse
194
+ # @param [String, #read, Array, Hash, Context] local_context
195
+ # @raise [JsonLdError]
196
+ # on a remote context load error, syntax error, or a reference to a term which is not defined.
197
+ # @return [Context]
198
+ def self.parse(local_context, **options)
199
+ self.new(options).parse(local_context)
200
+ end
201
+
189
202
  ##
190
203
  # Create new evaluation context
191
204
  # @param [Hash] options
@@ -204,7 +217,7 @@ module JSON::LD
204
217
  # @yield [ec]
205
218
  # @yieldparam [Context]
206
219
  # @return [Context]
207
- def initialize(options = {})
220
+ def initialize(**options)
208
221
  if options[:base]
209
222
  @base = @doc_base = RDF::URI(options[:base]).dup
210
223
  @doc_base.canonicalize! if options[:canonicalize]
@@ -298,6 +311,7 @@ module JSON::LD
298
311
  # @param [String, #read, Array, Hash, Context] local_context
299
312
  # @raise [JsonLdError]
300
313
  # on a remote context load error, syntax error, or a reference to a term which is not defined.
314
+ # @return [Context]
301
315
  # @see http://json-ld.org/spec/latest/json-ld-api/index.html#context-processing-algorithm
302
316
  def parse(local_context, remote_contexts = [])
303
317
  result = self.dup
@@ -391,9 +405,9 @@ module JSON::LD
391
405
  # If context has a @vocab member: if its value is not a valid absolute IRI or null trigger an INVALID_VOCAB_MAPPING error; otherwise set the active context's vocabulary mapping to its value and remove the @vocab member from context.
392
406
  context = context.dup # keep from modifying a hash passed as a param
393
407
  {
394
- '@base' => :base=,
408
+ '@base' => :base=,
395
409
  '@language' => :default_language=,
396
- '@vocab' => :vocab=
410
+ '@vocab' => :vocab=,
397
411
  }.each do |key, setter|
398
412
  v = context.fetch(key, false)
399
413
  unless v == false
@@ -797,16 +811,14 @@ module JSON::LD
797
811
  #
798
812
  # @param [String] value
799
813
  # A keyword, term, prefix:suffix or possibly relative IRI
800
- # @param [Hash{Symbol => Object}] options
801
814
  # @param [Boolean] documentRelative (false)
802
815
  # @param [Boolean] vocab (false)
803
- # @param [RDF::URI] base
804
816
  # @param [Hash] local_context
805
817
  # Used during Context Processing.
806
818
  # @param [Hash] defined
807
819
  # Used during Context Processing.
808
820
  # @param [Boolean] quiet (false)
809
- # @param [Hash] options ({})
821
+ # @param [Hash{Symbol => Object}] options
810
822
  # @return [RDF::URI, String]
811
823
  # IRI or String, if it's a keyword
812
824
  # @raise [JSON::LD::JsonLdError::InvalidIRIMapping] if the value cannot be expanded
@@ -878,7 +890,7 @@ module JSON::LD
878
890
  # @param [RDF::URI] iri
879
891
  # @param [Object] value
880
892
  # Value, used to select among various maps for the same IRI
881
- # @param [Boolean] :vocab
893
+ # @param [Boolean] vocab
882
894
  # specifies whether the passed iri should be compacted using the active context's vocabulary mapping
883
895
  # @param [Boolean] reverse
884
896
  # specifies whether a reverse property is being compacted
@@ -1193,7 +1205,7 @@ module JSON::LD
1193
1205
  defn << "vocab: #{self.vocab.to_s.inspect}" if self.vocab
1194
1206
  term_defs = term_definitions.map do |term, td|
1195
1207
  " " + term.inspect + " => " + td.to_rb
1196
- end
1208
+ end.sort
1197
1209
  defn << "term_definitions: {\n#{term_defs.join(",\n") }\n }" unless term_defs.empty?
1198
1210
  %(# -*- encoding: utf-8 -*-
1199
1211
  # frozen_string_literal: true
@@ -14,10 +14,9 @@ module JSON::LD
14
14
  # @param [Context] context
15
15
  # @param [Boolean] ordered (true)
16
16
  # Ensure output objects have keys ordered properly
17
- # @param [Boolean] framing (false)
18
- # @param [Boolean] keep_free_floating_notes (false)
19
- # @return [Array, Hash]
20
- def expand(input, active_property, context, ordered: true, framing: false, keep_free_floating_nodes: false)
17
+ # @return [Array<Hash{String => Object}>]
18
+ def expand(input, active_property, context, ordered: true)
19
+ framing = @options[:processingMode].include?("expand-frame")
21
20
  #log_debug("expand") {"input: #{input.inspect}, active_property: #{active_property.inspect}, context: #{context.inspect}"}
22
21
  result = case input
23
22
  when Array
@@ -73,18 +72,37 @@ module JSON::LD
73
72
  expanded_value = case expanded_property
74
73
  when '@id'
75
74
  # If expanded property is @id and value is not a string, an invalid @id value error has been detected and processing is aborted
76
- case value
75
+ e_id = case value
77
76
  when String
77
+ context.expand_iri(value, documentRelative: true, log_depth: @options[:log_depth]).to_s
78
+ when Array
79
+ # Framing allows an array of IRIs, and always puts values in an array
80
+ raise JsonLdError::InvalidIdValue,
81
+ "value of @id must be a string, array of string or hash if framing: #{value.inspect}" unless framing
82
+ context.expand_iri(value, documentRelative: true, log_depth: @options[:log_depth]).to_s
83
+ value.map do |v|
84
+ raise JsonLdError::InvalidTypeValue,
85
+ "@id value must be a string or array of strings for framing: #{v.inspect}" unless v.is_a?(String)
86
+ context.expand_iri(v, documentRelative: true, quiet: true, log_depth: @options[:log_depth]).to_s
87
+ end
78
88
  when Hash
79
89
  raise JsonLdError::InvalidIdValue,
80
90
  "value of @id must be a string unless framing: #{value.inspect}" unless framing
91
+ raise JsonLdError::InvalidTypeValue,
92
+ "value of @id must be a an empty object for framing: #{value.inspect}" unless
93
+ value.empty? && framing
94
+ [{}]
81
95
  else
82
96
  raise JsonLdError::InvalidIdValue,
83
97
  "value of @id must be a string or hash if framing: #{value.inspect}"
84
98
  end
85
99
 
86
- # Otherwise, set expanded value to the result of using the IRI Expansion algorithm, passing active context, value, and true for document relative.
87
- context.expand_iri(value, documentRelative: true, log_depth: @options[:log_depth]).to_s
100
+ # Use array form if framing
101
+ if framing && !e_id.is_a?(Array)
102
+ [e_id]
103
+ else
104
+ e_id
105
+ end
88
106
  when '@type'
89
107
  # If expanded property is @type and value is neither a string nor an array of strings, an invalid type value error has been detected and processing is aborted. Otherwise, set expanded value to the result of using the IRI Expansion algorithm, passing active context, true for vocab, and true for document relative to expand the value or each of its items.
90
108
  #log_debug("@type") {"value: #{value.inspect}"}
@@ -101,28 +119,55 @@ module JSON::LD
101
119
  # For framing
102
120
  raise JsonLdError::InvalidTypeValue,
103
121
  "@type value must be a an empty object for framing: #{value.inspect}" unless
104
- value.empty?
122
+ value.empty? && framing
123
+ [{}]
105
124
  else
106
125
  raise JsonLdError::InvalidTypeValue,
107
126
  "@type value must be a string or array of strings: #{value.inspect}"
108
127
  end
109
128
  when '@graph'
110
129
  # If expanded property is @graph, set expanded value to the result of using this algorithm recursively passing active context, @graph for active property, and value for element.
111
- expand(value, '@graph', context, ordered: ordered)
130
+ value = expand(value, '@graph', context, ordered: ordered)
131
+ value.is_a?(Array) ? value : [value]
112
132
  when '@value'
113
133
  # If expanded property is @value and value is not a scalar or null, an invalid value object value error has been detected and processing is aborted. Otherwise, set expanded value to value. If expanded value is null, set the @value member of result to null and continue with the next key from element. Null values need to be preserved in this case as the meaning of an @type member depends on the existence of an @value member.
114
- raise JsonLdError::InvalidValueObjectValue,
115
- "Value of #{expanded_property} must be a scalar or null: #{value.inspect}" if value.is_a?(Hash) || value.is_a?(Array)
116
- if value.nil?
134
+ # If framing, always use array form, unless null
135
+ case value
136
+ when String, TrueClass, FalseClass, Numeric then (framing ? [value] : value)
137
+ when nil
117
138
  output_object['@value'] = nil
118
139
  next;
140
+ when Array
141
+ raise JsonLdError::InvalidValueObjectValue,
142
+ "@value value may not be an array unless framing: #{value.inspect}" unless framing
143
+ value
144
+ when Hash
145
+ raise JsonLdError::InvalidValueObjectValue,
146
+ "@value value must be a an empty object for framing: #{value.inspect}" unless
147
+ value.empty? && framing
148
+ [value]
149
+ else
150
+ raise JsonLdError::InvalidValueObjectValue,
151
+ "Value of #{expanded_property} must be a scalar or null: #{value.inspect}"
119
152
  end
120
- value
121
153
  when '@language'
122
154
  # If expanded property is @language and value is not a string, an invalid language-tagged string error has been detected and processing is aborted. Otherwise, set expanded value to lowercased value.
123
- raise JsonLdError::InvalidLanguageTaggedString,
124
- "Value of #{expanded_property} must be a string: #{value.inspect}" unless value.is_a?(String)
125
- value.downcase
155
+ # If framing, always use array form, unless null
156
+ case value
157
+ when String then (framing ? [value.downcase] : value.downcase)
158
+ when Array
159
+ raise JsonLdError::InvalidLanguageTaggedString,
160
+ "@language value may not be an array unless framing: #{value.inspect}" unless framing
161
+ value.map(&:downcase)
162
+ when Hash
163
+ raise JsonLdError::InvalidLanguageTaggedString,
164
+ "@language value must be a an empty object for framing: #{value.inspect}" unless
165
+ value.empty? && framing
166
+ [value]
167
+ else
168
+ raise JsonLdError::InvalidLanguageTaggedString,
169
+ "Value of #{expanded_property} must be a string: #{value.inspect}"
170
+ end
126
171
  when '@index'
127
172
  # If expanded property is @index and value is not a string, an invalid @index value error has been detected and processing is aborted. Otherwise, set expanded value to value.
128
173
  raise JsonLdError::InvalidIndexValue,
@@ -186,7 +231,8 @@ module JSON::LD
186
231
 
187
232
  # Continue with the next key from element
188
233
  next
189
- when '@explicit', '@default', '@embed', '@explicit', '@omitDefault', '@preserve', '@requireAll'
234
+ when '@default', '@embed', '@explicit', '@omitDefault', '@preserve', '@requireAll'
235
+ next unless framing
190
236
  # Framing keywords
191
237
  [expand(value, expanded_property, context, ordered: ordered)].flatten
192
238
  else
@@ -291,21 +337,22 @@ module JSON::LD
291
337
  "value object has unknown keys: #{output_object.inspect}"
292
338
  end
293
339
 
294
- output_object.delete('@language') if output_object['@language'].to_s.empty?
295
- output_object.delete('@type') if output_object['@type'].to_s.empty?
340
+ output_object.delete('@language') if Array(output_object['@language']).join('').to_s.empty?
341
+ output_object.delete('@type') if Array(output_object['@type']).join('').to_s.empty?
296
342
 
297
343
  # If the value of result's @value key is null, then set result to null.
298
- return nil if output_object['@value'].nil?
344
+ return nil if Array(output_object['@value']).empty?
299
345
 
300
- if !output_object['@value'].is_a?(String) && output_object.has_key?('@language')
346
+ if !Array(output_object['@value']).all? {|v| v.is_a?(String) || v.is_a?(Hash) && v.empty?} && output_object.has_key?('@language')
301
347
  # Otherwise, if the value of result's @value member is not a string and result contains the key @language, an invalid language-tagged value error has been detected (only strings can be language-tagged) and processing is aborted.
302
348
  raise JsonLdError::InvalidLanguageTaggedValue,
303
- "when @language is used, @value must be a string: #{@value.inspect}"
304
- elsif !output_object.fetch('@type', "").is_a?(String) ||
305
- !context.expand_iri(output_object.fetch('@type', ""), vocab: true, log_depth: @options[:log_depth]).is_a?(RDF::URI)
349
+ "when @language is used, @value must be a string: #{output_object.inspect}"
350
+ elsif !Array(output_object.fetch('@type', "")).all? {|t|
351
+ t.is_a?(String) && context.expand_iri(t, vocab: true, log_depth: @options[:log_depth]).is_a?(RDF::URI) ||
352
+ t.is_a?(Hash) && t.empty?}
306
353
  # Otherwise, if the result has a @type member and its value is not an IRI, an invalid typed value error has been detected and processing is aborted.
307
354
  raise JsonLdError::InvalidTypedValue,
308
- "value of @type must be an IRI: #{output_object['@type'].inspect}"
355
+ "value of @type must be an IRI: #{output_object.inspect}"
309
356
  end
310
357
  elsif !output_object.fetch('@type', []).is_a?(Array)
311
358
  # Otherwise, if result contains the key @type and its associated value is not an array, set it to an array containing only the associated value.
@@ -327,7 +374,7 @@ module JSON::LD
327
374
  # If active property is null or @graph, drop free-floating values as follows:
328
375
  if (active_property || '@graph') == '@graph' &&
329
376
  (output_object.keys.any? {|k| %w(@value @list).include?(k)} ||
330
- (output_object.keys - %w(@id)).empty? && !keep_free_floating_nodes)
377
+ (output_object.keys - %w(@id)).empty? && !framing)
331
378
  #log_debug(" =>") { "empty top-level: " + output_object.inspect}
332
379
  return nil
333
380
  end