json-ld 3.1.6 → 3.1.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -253,6 +253,7 @@ module JSON::LD
253
253
 
254
254
  local_context = as_array(local_context)
255
255
 
256
+ log_depth do
256
257
  local_context.each do |context|
257
258
  case context
258
259
  when nil,false
@@ -266,22 +267,22 @@ module JSON::LD
266
267
  "Attempt to clear a context with protected terms"
267
268
  end
268
269
  when Context
269
- #log_debug("parse") {"context: #{context.inspect}"}
270
+ log_debug("parse") {"context: #{context.inspect}"}
270
271
  result = result.merge(context)
271
272
  when IO, StringIO
272
- #log_debug("parse") {"io: #{context}"}
273
+ log_debug("parse") {"io: #{context}"}
273
274
  # Load context document, if it is an open file
274
275
  begin
275
276
  ctx = JSON.load(context)
276
277
  raise JSON::LD::JsonLdError::InvalidRemoteContext, "Context missing @context key" if @options[:validate] && ctx['@context'].nil?
277
278
  result = result.parse(ctx["@context"] ? ctx["@context"] : {})
278
279
  rescue JSON::ParserError => e
279
- #log_debug("parse") {"Failed to parse @context from remote document at #{context}: #{e.message}"}
280
+ log_info("parse") {"Failed to parse @context from remote document at #{context}: #{e.message}"}
280
281
  raise JSON::LD::JsonLdError::InvalidRemoteContext, "Failed to parse remote context at #{context}: #{e.message}" if @options[:validate]
281
282
  self
282
283
  end
283
284
  when String, RDF::URI
284
- #log_debug("parse") {"remote: #{context}, base: #{result.context_base || result.base}"}
285
+ log_debug("parse") {"remote: #{context}, base: #{result.context_base || result.base}"}
285
286
 
286
287
  # 3.2.1) Set context to the result of resolving value against the base IRI which is established as specified in section 5.1 Establishing a Base URI of [RFC3986]. Only the basic algorithm in section 5.2 of [RFC3986] is used; neither Syntax-Based Normalization nor Scheme-Based Normalization are performed. Characters additionally allowed in IRI references are treated in the same way that unreserved characters are treated in URI references, per section 6.5 of [RFC3987].
287
288
  context = RDF::URI(result.context_base || base).join(context)
@@ -296,11 +297,11 @@ module JSON::LD
296
297
 
297
298
  cached_context = if PRELOADED[context_canon.to_s]
298
299
  # If we have a cached context, merge it into the current context (result) and use as the new context
299
- #log_debug("parse") {"=> cached_context: #{context_canon.to_s.inspect}"}
300
+ log_debug("parse") {"=> cached_context: #{context_canon.to_s.inspect}"}
300
301
 
301
302
  # If this is a Proc, then replace the entry with the result of running the Proc
302
303
  if PRELOADED[context_canon.to_s].respond_to?(:call)
303
- #log_debug("parse") {"=> (call)"}
304
+ log_debug("parse") {"=> (call)"}
304
305
  PRELOADED[context_canon.to_s] = PRELOADED[context_canon.to_s].call
305
306
  end
306
307
  PRELOADED[context_canon.to_s]
@@ -314,22 +315,23 @@ module JSON::LD
314
315
  #context_opts.delete(:headers)
315
316
  JSON::LD::API.loadRemoteDocument(context.to_s, **context_opts) do |remote_doc|
316
317
  # 3.2.5) Dereference context. If the dereferenced document has no top-level JSON object with an @context member, an invalid remote context has been detected and processing is aborted; otherwise, set context to the value of that member.
317
- raise JsonLdError::InvalidRemoteContext, "#{context}" unless remote_doc.document.is_a?(Hash) && remote_doc.document.has_key?('@context')
318
+ raise JsonLdError::InvalidRemoteContext, "#{context}" unless remote_doc.document.is_a?(Hash) && remote_doc.document.key?('@context')
318
319
 
319
320
  # Parse stand-alone
320
321
  ctx = Context.new(unfrozen: true, **options).dup
321
322
  ctx.context_base = context.to_s
322
323
  ctx = ctx.parse(remote_doc.document['@context'], remote_contexts: remote_contexts.dup)
324
+ ctx.context_base = context.to_s # In case it was altered
323
325
  ctx.instance_variable_set(:@base, nil)
324
326
  ctx
325
327
  end
326
328
  rescue JsonLdError::LoadingDocumentFailed => e
327
- #log_debug("parse") {"Failed to retrieve @context from remote document at #{context_no_base.context_base.inspect}: #{e.message}"}
329
+ log_info("parse") {"Failed to retrieve @context from remote document at #{context_canon.inspect}: #{e.message}"}
328
330
  raise JsonLdError::LoadingRemoteContextFailed, "#{context}: #{e.message}", e.backtrace
329
331
  rescue JsonLdError
330
332
  raise
331
333
  rescue StandardError => e
332
- #log_debug("parse") {"Failed to retrieve @context from remote document at #{context_no_base.context_base.inspect}: #{e.message}"}
334
+ log_info("parse") {"Failed to retrieve @context from remote document at #{context_canon.inspect}: #{e.message}"}
333
335
  raise JsonLdError::LoadingRemoteContextFailed, "#{context}: #{e.message}", e.backtrace
334
336
  end
335
337
  end
@@ -352,7 +354,7 @@ module JSON::LD
352
354
  '@propagate' => :propagate=,
353
355
  '@vocab' => :vocab=,
354
356
  }.each do |key, setter|
355
- next unless context.has_key?(key)
357
+ next unless context.key?(key)
356
358
  if key == '@import'
357
359
  # Retrieve remote context and merge the remaining context object into the result.
358
360
  raise JsonLdError::InvalidContextEntry, "@import may only be used in 1.1 mode}" if result.processingMode("json-ld-1.0")
@@ -367,11 +369,11 @@ module JSON::LD
367
369
  # FIXME: should cache this, but ContextCache is for parsed contexts
368
370
  JSON::LD::API.loadRemoteDocument(import_loc, **context_opts) do |remote_doc|
369
371
  # Dereference import_loc. If the dereferenced document has no top-level JSON object with an @context member, an invalid remote context has been detected and processing is aborted; otherwise, set context to the value of that member.
370
- raise JsonLdError::InvalidRemoteContext, "#{import_loc}" unless remote_doc.document.is_a?(Hash) && remote_doc.document.has_key?('@context')
372
+ raise JsonLdError::InvalidRemoteContext, "#{import_loc}" unless remote_doc.document.is_a?(Hash) && remote_doc.document.key?('@context')
371
373
  import_context = remote_doc.document['@context']
372
374
  import_context.delete('@base')
373
375
  raise JsonLdError::InvalidRemoteContext, "#{import_context.to_json} must be an object" unless import_context.is_a?(Hash)
374
- raise JsonLdError::InvalidContextEntry, "#{import_context.to_json} must not include @import entry" if import_context.has_key?('@import')
376
+ raise JsonLdError::InvalidContextEntry, "#{import_context.to_json} must not include @import entry" if import_context.key?('@import')
375
377
  context.delete(key)
376
378
  context = import_context.merge(context)
377
379
  end
@@ -406,6 +408,7 @@ module JSON::LD
406
408
  raise JsonLdError::InvalidLocalContext, "must be a URL, JSON object or array of same: #{context.inspect}"
407
409
  end
408
410
  end
411
+ end
409
412
  result
410
413
  end
411
414
 
@@ -475,7 +478,7 @@ module JSON::LD
475
478
  remote_contexts: [],
476
479
  validate_scoped: true)
477
480
  # Expand a string value, unless it matches a keyword
478
- #log_debug("create_term_definition") {"term = #{term.inspect}"}
481
+ log_debug("create_term_definition") {"term = #{term.inspect}"}
479
482
 
480
483
  # If defined contains the key term, then the associated value must be true, indicating that the term definition has already been created, so return. Otherwise, a cyclical term definition has been detected, which is an error.
481
484
  case defined[term]
@@ -542,7 +545,7 @@ module JSON::LD
542
545
  # Potentially note that the term is protected
543
546
  definition.protected = value.fetch('@protected', protected)
544
547
 
545
- if value.has_key?('@type')
548
+ if value.key?('@type')
546
549
  type = value['@type']
547
550
  # SPEC FIXME: @type may be nil
548
551
  type = case type
@@ -566,7 +569,7 @@ module JSON::LD
566
569
  definition.type_mapping = type
567
570
  end
568
571
 
569
- if value.has_key?('@reverse')
572
+ if value.key?('@reverse')
570
573
  raise JsonLdError::InvalidReverseProperty, "unexpected key in #{value.inspect} on term #{term.inspect}" if
571
574
  value.key?('@id') || value.key?('@nest')
572
575
  raise JsonLdError::InvalidIRIMapping, "expected value of @reverse to be a string: #{value['@reverse'].inspect} on term #{term.inspect}" unless
@@ -592,7 +595,7 @@ module JSON::LD
592
595
  warn "[DEPRECATION] Blank Node terms deprecated in JSON-LD 1.1." if @options[:validate] && processingMode('json-ld-1.1') && definition.id.to_s.start_with?("_:")
593
596
 
594
597
  # If value contains an @container member, set the container mapping of definition to its value; if its value is neither @set, @index, @type, @id, an absolute IRI nor null, an invalid reverse property error has been detected (reverse properties only support set- and index-containers) and processing is aborted.
595
- if value.has_key?('@container')
598
+ if value.key?('@container')
596
599
  container = value['@container']
597
600
  raise JsonLdError::InvalidReverseProperty,
598
601
  "unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" unless
@@ -600,9 +603,9 @@ module JSON::LD
600
603
  definition.container_mapping = check_container(container, local_context, defined, term)
601
604
  end
602
605
  definition.reverse_property = true
603
- elsif value.has_key?('@id') && value['@id'].nil?
606
+ elsif value.key?('@id') && value['@id'].nil?
604
607
  # Allowed to reserve a null term, which may be protected
605
- elsif value.has_key?('@id') && value['@id'] != term
608
+ elsif value.key?('@id') && value['@id'] != term
606
609
  raise JsonLdError::InvalidIRIMapping, "expected value of @id to be a string: #{value['@id'].inspect} on term #{term.inspect}" unless
607
610
  value['@id'].is_a?(String)
608
611
 
@@ -637,7 +640,7 @@ module JSON::LD
637
640
  elsif term[1..-1].include?(':')
638
641
  # If term is a compact IRI with a prefix that is a key in local context then a dependency has been found. Use this algorithm recursively passing active context, local context, the prefix as term, and defined.
639
642
  prefix, suffix = term.split(':', 2)
640
- create_term_definition(local_context, prefix, defined, protected: protected) if local_context.has_key?(prefix)
643
+ create_term_definition(local_context, prefix, defined, protected: protected) if local_context.key?(prefix)
641
644
 
642
645
  definition.id = if td = term_definitions[prefix]
643
646
  # If term's prefix has a term definition in active context, set the IRI mapping for definition to the result of concatenating the value associated with the prefix's IRI mapping and the term's suffix.
@@ -646,7 +649,7 @@ module JSON::LD
646
649
  # Otherwise, term is an absolute IRI. Set the IRI mapping for definition to term
647
650
  term
648
651
  end
649
- #log_debug("") {"=> #{definition.id}"}
652
+ log_debug("") {"=> #{definition.id}"}
650
653
  elsif term.include?('/')
651
654
  # If term is a relative IRI
652
655
  definition.id = expand_iri(term, vocab: true)
@@ -659,12 +662,12 @@ module JSON::LD
659
662
  # Otherwise, active context must have a vocabulary mapping, otherwise an invalid value has been detected, which is an error. Set the IRI mapping for definition to the result of concatenating the value associated with the vocabulary mapping and term.
660
663
  raise JsonLdError::InvalidIRIMapping, "relative term definition without vocab: #{term} on term #{term.inspect}" unless vocab
661
664
  definition.id = vocab + term
662
- #log_debug("") {"=> #{definition.id}"}
665
+ log_debug("") {"=> #{definition.id}"}
663
666
  end
664
667
 
665
668
  @iri_to_term[definition.id] = term if simple_term && definition.id
666
669
 
667
- if value.has_key?('@container')
670
+ if value.key?('@container')
668
671
  #log_debug("") {"container_mapping: #{value['@container'].inspect}"}
669
672
  definition.container_mapping = check_container(value['@container'], local_context, defined, term)
670
673
 
@@ -679,14 +682,14 @@ module JSON::LD
679
682
  end
680
683
  end
681
684
 
682
- if value.has_key?('@index')
685
+ if value.key?('@index')
683
686
  # property-based indexing
684
687
  raise JsonLdError::InvalidTermDefinition, "@index without @index in @container: #{value['@index']} on term #{term.inspect}" unless definition.container_mapping.include?('@index')
685
688
  raise JsonLdError::InvalidTermDefinition, "@index must expand to an IRI: #{value['@index']} on term #{term.inspect}" unless value['@index'].is_a?(String) && !value['@index'].start_with?('@')
686
689
  definition.index = value['@index'].to_s
687
690
  end
688
691
 
689
- if value.has_key?('@context')
692
+ if value.key?('@context')
690
693
  begin
691
694
  new_ctx = self.parse(value['@context'],
692
695
  base: base,
@@ -699,12 +702,13 @@ module JSON::LD
699
702
  when nil then [nil]
700
703
  else value['@context']
701
704
  end
705
+ log_debug("") {"context: #{definition.context.inspect}"}
702
706
  rescue JsonLdError => e
703
707
  raise JsonLdError::InvalidScopedContext, "Term definition for #{term.inspect} contains illegal value for @context: #{e.message}"
704
708
  end
705
709
  end
706
710
 
707
- if value.has_key?('@language')
711
+ if value.key?('@language')
708
712
  language = value['@language']
709
713
  language = case value['@language']
710
714
  when String
@@ -722,14 +726,14 @@ module JSON::LD
722
726
  definition.language_mapping = language || false
723
727
  end
724
728
 
725
- if value.has_key?('@direction')
729
+ if value.key?('@direction')
726
730
  direction = value['@direction']
727
731
  raise JsonLdError::InvalidBaseDirection, "direction must be null, 'ltr', or 'rtl', was #{language.inspect}} on term #{term.inspect}" unless direction.nil? || %w(ltr rtl).include?(direction)
728
732
  #log_debug("") {"direction_mapping: #{direction.inspect}"}
729
733
  definition.direction_mapping = direction || false
730
734
  end
731
735
 
732
- if value.has_key?('@nest')
736
+ if value.key?('@nest')
733
737
  nest = value['@nest']
734
738
  raise JsonLdError::InvalidNestValue, "nest must be a string, was #{nest.inspect}} on term #{term.inspect}" unless nest.is_a?(String)
735
739
  raise JsonLdError::InvalidNestValue, "nest must not be a keyword other than @nest, was #{nest.inspect}} on term #{term.inspect}" if nest.match?(/^@[a-zA-Z]+$/) && nest != '@nest'
@@ -737,7 +741,7 @@ module JSON::LD
737
741
  definition.nest = nest
738
742
  end
739
743
 
740
- if value.has_key?('@prefix')
744
+ if value.key?('@prefix')
741
745
  raise JsonLdError::InvalidTermDefinition, "@prefix used on compact or relative IRI term #{term.inspect}" if term.match?(%r{:|/})
742
746
  case pfx = value['@prefix']
743
747
  when TrueClass, FalseClass
@@ -1018,7 +1022,7 @@ module JSON::LD
1018
1022
 
1019
1023
  term_sym = term.empty? ? "" : term.to_sym
1020
1024
  iri_to_term.delete(term_definitions[term].id.to_s) if term_definitions[term].id.is_a?(String)
1021
- @options[:prefixes][term_sym] = value if @options.has_key?(:prefixes)
1025
+ @options[:prefixes][term_sym] = value if @options.key?(:prefixes)
1022
1026
  iri_to_term[value.to_s] = term
1023
1027
  term_definitions[term]
1024
1028
  end
@@ -1038,7 +1042,7 @@ module JSON::LD
1038
1042
  # @param [Term, #to_s] term in unexpanded form
1039
1043
  # @return [Array<'@index', '@language', '@index', '@set', '@type', '@id', '@graph'>]
1040
1044
  def container(term)
1041
- return [term] if term == '@list'
1045
+ return Set[term] if term == '@list'
1042
1046
  term = find_definition(term)
1043
1047
  term ? term.container_mapping : Set.new
1044
1048
  end
@@ -1134,7 +1138,7 @@ module JSON::LD
1134
1138
  # @return [Term] related term definition
1135
1139
  def reverse_term(term)
1136
1140
  # Direct lookup of term
1137
- term = term_definitions[term.to_s] if term_definitions.has_key?(term.to_s) && !term.is_a?(TermDefinition)
1141
+ term = term_definitions[term.to_s] if term_definitions.key?(term.to_s) && !term.is_a?(TermDefinition)
1138
1142
 
1139
1143
  # Lookup term, assuming term is an IRI
1140
1144
  unless term.is_a?(TermDefinition)
@@ -1182,7 +1186,7 @@ module JSON::LD
1182
1186
  defined = defined || {} # if we initialized in the keyword arg we would allocate {} at each invokation, even in the 2 (common) early returns above.
1183
1187
 
1184
1188
  # If local context is not null, it contains a key that equals value, and the value associated with the key that equals value in defined is not true, then invoke the Create Term Definition subalgorithm, passing active context, local context, value as term, and defined. This will ensure that a term definition is created for value in active context during Context Processing.
1185
- if local_context && local_context.has_key?(value) && !defined[value]
1189
+ if local_context && local_context.key?(value) && !defined[value]
1186
1190
  create_term_definition(local_context, value, defined)
1187
1191
  end
1188
1192
 
@@ -1212,7 +1216,7 @@ module JSON::LD
1212
1216
  end
1213
1217
 
1214
1218
  # If local context is not null, it contains a key that equals prefix, and the value associated with the key that equals prefix in defined is not true, invoke the Create Term Definition algorithm, passing active context, local context, prefix as term, and defined. This will ensure that a term definition is created for prefix in active context during Context Processing.
1215
- if local_context && local_context.has_key?(prefix) && !defined[prefix]
1219
+ if local_context && local_context.key?(prefix) && !defined[prefix]
1216
1220
  create_term_definition(local_context, prefix, defined)
1217
1221
  end
1218
1222
 
@@ -1287,7 +1291,7 @@ module JSON::LD
1287
1291
  return if iri.nil?
1288
1292
  iri = iri.to_s
1289
1293
 
1290
- if vocab && inverse_context.has_key?(iri)
1294
+ if vocab && inverse_context.key?(iri)
1291
1295
  default_language = if self.default_direction
1292
1296
  "#{self.default_language}_#{self.default_direction}".downcase
1293
1297
  else
@@ -1298,7 +1302,7 @@ module JSON::LD
1298
1302
  containers.concat(CONTAINERS_INDEX_SET) if index?(value) && !graph?(value)
1299
1303
 
1300
1304
  # If the value is a JSON Object with the key @preserve, use the value of @preserve.
1301
- value = value['@preserve'].first if value.is_a?(Hash) && value.has_key?('@preserve')
1305
+ value = value['@preserve'].first if value.is_a?(Hash) && value.key?('@preserve')
1302
1306
 
1303
1307
  if reverse
1304
1308
  tl, tl_value = "@type", "@reverse"
@@ -1312,11 +1316,11 @@ module JSON::LD
1312
1316
  list.each do |item|
1313
1317
  item_language, item_type = "@none", "@none"
1314
1318
  if value?(item)
1315
- if item.has_key?('@direction')
1319
+ if item.key?('@direction')
1316
1320
  item_language = "#{item['@language']}_#{item['@direction']}".downcase
1317
- elsif item.has_key?('@language')
1321
+ elsif item.key?('@language')
1318
1322
  item_language = item['@language'].downcase
1319
- elsif item.has_key?('@type')
1323
+ elsif item.key?('@type')
1320
1324
  item_type = item['@type']
1321
1325
  else
1322
1326
  item_language = "@null"
@@ -1344,14 +1348,14 @@ module JSON::LD
1344
1348
  elsif graph?(value)
1345
1349
  # Prefer @index and @id containers, then @graph, then @index
1346
1350
  containers.concat(CONTAINERS_GRAPH_INDEX_INDEX) if index?(value)
1347
- containers.concat(CONTAINERS_GRAPH) if value.has_key?('@id')
1351
+ containers.concat(CONTAINERS_GRAPH) if value.key?('@id')
1348
1352
 
1349
1353
  # Prefer an @graph container next
1350
1354
  containers.concat(CONTAINERS_GRAPH_SET)
1351
1355
 
1352
1356
  # Lastly, in 1.1, any graph can be indexed on @index or @id, so add if we haven't already
1353
1357
  containers.concat(CONTAINERS_GRAPH_INDEX) unless index?(value)
1354
- containers.concat(CONTAINERS_GRAPH) unless value.has_key?('@id')
1358
+ containers.concat(CONTAINERS_GRAPH) unless value.key?('@id')
1355
1359
  containers.concat(CONTAINERS_INDEX_SET) unless index?(value)
1356
1360
  containers << '@set'
1357
1361
 
@@ -1359,13 +1363,13 @@ module JSON::LD
1359
1363
  else
1360
1364
  if value?(value)
1361
1365
  # In 1.1, an language map can be used to index values using @none
1362
- if value.has_key?('@language') && !index?(value)
1366
+ if value.key?('@language') && !index?(value)
1363
1367
  tl_value = value['@language'].downcase
1364
1368
  tl_value += "_#{value['@direction']}" if value['@direction']
1365
1369
  containers.concat(CONTAINERS_LANGUAGE)
1366
- elsif value.has_key?('@direction') && !index?(value)
1370
+ elsif value.key?('@direction') && !index?(value)
1367
1371
  tl_value = "_#{value['@direction']}"
1368
- elsif value.has_key?('@type')
1372
+ elsif value.key?('@type')
1369
1373
  tl_value = value['@type']
1370
1374
  tl = '@type'
1371
1375
  end
@@ -1387,7 +1391,7 @@ module JSON::LD
1387
1391
  tl_value ||= '@null'
1388
1392
  preferred_values = []
1389
1393
  preferred_values << '@reverse' if tl_value == '@reverse'
1390
- if (tl_value == '@id' || tl_value == '@reverse') && value.is_a?(Hash) && value.has_key?('@id')
1394
+ if (tl_value == '@id' || tl_value == '@reverse') && value.is_a?(Hash) && value.key?('@id')
1391
1395
  t_iri = compact_iri(value['@id'], vocab: true, base: base)
1392
1396
  if (r_td = term_definitions[t_iri]) && r_td.id == value['@id']
1393
1397
  preferred_values.concat(CONTAINERS_VOCAB_ID)
@@ -1413,7 +1417,7 @@ module JSON::LD
1413
1417
  # At this point, there is no simple term that iri can be compacted to. If vocab is true and active context has a vocabulary mapping:
1414
1418
  if vocab && self.vocab && iri.start_with?(self.vocab) && iri.length > self.vocab.length
1415
1419
  suffix = iri[self.vocab.length..-1]
1416
- return suffix unless term_definitions.has_key?(suffix)
1420
+ return suffix unless term_definitions.key?(suffix)
1417
1421
  end
1418
1422
 
1419
1423
  # The iri could not be compacted using the active context's vocabulary mapping. Try to create a compact IRI, starting by initializing compact IRI to null. This variable will be used to tore the created compact IRI, if any.
@@ -1427,7 +1431,7 @@ module JSON::LD
1427
1431
 
1428
1432
  suffix = iri[td.id.length..-1]
1429
1433
  ciri = "#{term}:#{suffix}"
1430
- candidates << ciri unless value && term_definitions.has_key?(ciri)
1434
+ candidates << ciri unless value && term_definitions.key?(ciri)
1431
1435
  end
1432
1436
 
1433
1437
  return candidates.sort.first if !candidates.empty?
@@ -1455,6 +1459,8 @@ module JSON::LD
1455
1459
  if !vocab
1456
1460
  # transform iri to a relative IRI using the document's base IRI
1457
1461
  iri = remove_base(self.base || base, iri)
1462
+ # Make . relative if it has the form of a keyword.
1463
+ iri = "./#{iri}" if iri.match?(/^@[a-zA-Z]+$/)
1458
1464
  return iri
1459
1465
  else
1460
1466
  return iri
@@ -1523,16 +1529,16 @@ module JSON::LD
1523
1529
  res['@language'] = lang
1524
1530
  end
1525
1531
  res['@direction'] = dir
1526
- elsif useNativeTypes && RDF_LITERAL_NATIVE_TYPES.include?(value.datatype)
1532
+ elsif useNativeTypes && RDF_LITERAL_NATIVE_TYPES.include?(value.datatype) && value.valid?
1527
1533
  res['@type'] = uri(coerce(property)) if coerce(property)
1528
1534
  res['@value'] = value.object
1529
1535
  else
1530
- value.canonicalize! if value.datatype == RDF::XSD.double
1536
+ value.canonicalize! if value.valid? && value.datatype == RDF::XSD.double
1531
1537
  if coerce(property)
1532
1538
  res['@type'] = uri(coerce(property)).to_s
1533
- elsif value.has_datatype?
1539
+ elsif value.datatype?
1534
1540
  res['@type'] = uri(value.datatype).to_s
1535
- elsif value.has_language? || language(property)
1541
+ elsif value.language? || language(property)
1536
1542
  res['@language'] = (value.language || language(property)).to_s
1537
1543
  end
1538
1544
  res['@value'] = value.to_s
@@ -1580,15 +1586,15 @@ module JSON::LD
1580
1586
  direction = direction(property)
1581
1587
 
1582
1588
  result = case
1583
- when coerce(property) == '@id' && value.has_key?('@id') && (value.keys - %w(@id @index)).empty?
1589
+ when coerce(property) == '@id' && value.key?('@id') && (value.keys - %w(@id @index)).empty?
1584
1590
  # Compact an @id coercion
1585
1591
  #log_debug("") {" (@id & coerce)"}
1586
1592
  compact_iri(value['@id'], base: base)
1587
- when coerce(property) == '@vocab' && value.has_key?('@id') && (value.keys - %w(@id @index)).empty?
1593
+ when coerce(property) == '@vocab' && value.key?('@id') && (value.keys - %w(@id @index)).empty?
1588
1594
  # Compact an @id coercion
1589
1595
  #log_debug("") {" (@id & coerce & vocab)"}
1590
1596
  compact_iri(value['@id'], vocab: true)
1591
- when value.has_key?('@id')
1597
+ when value.key?('@id')
1592
1598
  #log_debug("") {" (@id)"}
1593
1599
  # return value as is
1594
1600
  value
@@ -1609,7 +1615,7 @@ module JSON::LD
1609
1615
  value
1610
1616
  end
1611
1617
 
1612
- if result.is_a?(Hash) && result.has_key?('@type') && value['@type'] != '@json'
1618
+ if result.is_a?(Hash) && result.key?('@type') && value['@type'] != '@json'
1613
1619
  # Compact values of @type
1614
1620
  c_type = if result['@type'].is_a?(Array)
1615
1621
  result['@type'].map {|t| compact_iri(t, vocab: true)}
@@ -1857,11 +1863,11 @@ module JSON::LD
1857
1863
  container_map = inverse_context[iri]
1858
1864
  #log_debug(" ") {"container_map: #{container_map.inspect}"}
1859
1865
  containers.each do |container|
1860
- next unless container_map.has_key?(container)
1866
+ next unless container_map.key?(container)
1861
1867
  tl_map = container_map[container]
1862
1868
  value_map = tl_map[type_language]
1863
1869
  preferred_values.each do |item|
1864
- next unless value_map.has_key?(item)
1870
+ next unless value_map.key?(item)
1865
1871
  #log_debug("=>") {value_map[item].inspect}
1866
1872
  return value_map[item]
1867
1873
  end
@@ -1882,7 +1888,7 @@ module JSON::LD
1882
1888
  @base_and_parents ||= begin
1883
1889
  u = base
1884
1890
  iri_set = u.to_s.end_with?('/') ? [u.to_s] : []
1885
- iri_set << u.to_s while (u = u.parent)
1891
+ iri_set << u.to_s while (u != './' && u = u.parent)
1886
1892
  iri_set
1887
1893
  end
1888
1894
  b = base.to_s
@@ -11,9 +11,9 @@ module JSON::LD
11
11
  # The following constant is used to reduce object allocations
12
12
  CONTAINER_INDEX_ID_TYPE = Set['@index', '@id', '@type'].freeze
13
13
  KEY_ID = %w(@id).freeze
14
- KEYS_VALUE_LANGUAGE_TYPE_INDEX_DIRECTION = %w(@value @language @type @index @direction).freeze
14
+ KEYS_VALUE_LANGUAGE_TYPE_INDEX_DIRECTION = %w(@value @language @type @index @direction @annotation).freeze
15
15
  KEYS_SET_LIST_INDEX = %w(@set @list @index).freeze
16
- KEYS_INCLUDED_TYPE = %w(@included @type).freeze
16
+ KEYS_INCLUDED_TYPE_REVERSE = %w(@included @type @reverse).freeze
17
17
 
18
18
  ##
19
19
  # Expand an Array or Object given an active context and performing local context expansion.
@@ -49,7 +49,13 @@ module JSON::LD
49
49
  log_depth: log_depth.to_i + 1)
50
50
 
51
51
  # If the active property is @list or its container mapping is set to @list and v is an array, change it to a list object
52
- v = {"@list" => v} if is_list && v.is_a?(Array)
52
+ if is_list && v.is_a?(Array)
53
+ # Make sure that no member of v contains an annotation object
54
+ raise JsonLdError::InvalidAnnotation,
55
+ "A list element must not contain @annotation." if
56
+ v.any? {|n| n.is_a?(Hash) && n.key?('@annotation')}
57
+ v = {"@list" => v}
58
+ end
53
59
 
54
60
  case v
55
61
  when nil then nil
@@ -81,7 +87,7 @@ module JSON::LD
81
87
  log_debug("expand", depth: log_depth.to_i) {"after property_scoped_context: #{context.inspect}"} unless property_scoped_context.nil?
82
88
 
83
89
  # If element contains the key @context, set active context to the result of the Context Processing algorithm, passing active context and the value of the @context key as local context.
84
- if input.has_key?('@context')
90
+ if input.key?('@context')
85
91
  context = context.parse(input.delete('@context'), base: @options[:base])
86
92
  log_debug("expand", depth: log_depth.to_i) {"context: #{context.inspect}"}
87
93
  end
@@ -101,7 +107,7 @@ module JSON::LD
101
107
  Array(input[tk]).sort.each do |term|
102
108
  term_context = type_scoped_context.term_definitions[term].context if type_scoped_context.term_definitions[term]
103
109
  unless term_context.nil?
104
- log_debug("expand", depth: log_depth.to_i) {"term_context: #{term_context.inspect}"}
110
+ log_debug("expand", depth: log_depth.to_i) {"term_context[#{term}]: #{term_context.inspect}"}
105
111
  context = context.parse(term_context, base: @options[:base], propagate: false)
106
112
  end
107
113
  end
@@ -142,7 +148,7 @@ module JSON::LD
142
148
 
143
149
  if output_object['@type'] == '@json' && context.processingMode('json-ld-1.1')
144
150
  # Any value of @value is okay if @type: @json
145
- elsif !ary.all? {|v| v.is_a?(String) || v.is_a?(Hash) && v.empty?} && output_object.has_key?('@language')
151
+ elsif !ary.all? {|v| v.is_a?(String) || v.is_a?(Hash) && v.empty?} && output_object.key?('@language')
146
152
  # 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.
147
153
  raise JsonLdError::InvalidLanguageTaggedValue,
148
154
  "when @language is used, @value must be a string: #{output_object.inspect}"
@@ -172,6 +178,18 @@ module JSON::LD
172
178
 
173
179
  # If result contains the key @set, then set result to the key's associated value.
174
180
  return output_object['@set'] if output_object.key?('@set')
181
+ elsif output_object['@annotation']
182
+ # Otherwise, if result contains the key @annotation,
183
+ # the array value must all be node objects without an @id property, otherwise, an invalid annotation error has been detected and processing is aborted.
184
+ raise JsonLdError::InvalidAnnotation,
185
+ "@annotation must reference node objects without @id." unless
186
+ output_object['@annotation'].all? {|o| node?(o) && !o.key?('@id')}
187
+
188
+ # Additionally, the property must not be used if there is no active property, or the expanded active property is @graph.
189
+ raise JsonLdError::InvalidAnnotation,
190
+ "@annotation must not be used on a top-level object." if
191
+ %w(@graph @included).include?(expanded_active_property || '@graph')
192
+
175
193
  end
176
194
 
177
195
  # If result contains only the key @language, set result to null.
@@ -240,10 +258,10 @@ module JSON::LD
240
258
  expanded_property.to_s.start_with?("_:") &&
241
259
  context.processingMode('json-ld-1.1')
242
260
 
243
- #log_debug("expand property", depth: log_depth.to_i) {"ap: #{active_property.inspect}, expanded: #{expanded_property.inspect}, value: #{value.inspect}"}
261
+ log_debug("expand property", depth: log_depth.to_i) {"ap: #{active_property.inspect}, expanded: #{expanded_property.inspect}, value: #{value.inspect}"}
244
262
 
245
263
  if expanded_property.nil?
246
- #log_debug(" => ", depth: log_depth.to_i) {"skip nil property"}
264
+ log_debug(" => ", depth: log_depth.to_i) {"skip nil property"}
247
265
  next
248
266
  end
249
267
 
@@ -254,10 +272,15 @@ module JSON::LD
254
272
 
255
273
  # If result has already an expanded property member (other than @type), an colliding keywords error has been detected and processing is aborted.
256
274
  raise JsonLdError::CollidingKeywords,
257
- "#{expanded_property} already exists in result" if output_object.has_key?(expanded_property) && !KEYS_INCLUDED_TYPE.include?(expanded_property)
275
+ "#{expanded_property} already exists in result" if output_object.key?(expanded_property) && !KEYS_INCLUDED_TYPE_REVERSE.include?(expanded_property)
258
276
 
259
277
  expanded_value = case expanded_property
260
278
  when '@id'
279
+ # If expanded active property is `@annotation`, an invalid annotation error has been found and processing is aborted.
280
+ raise JsonLdError::InvalidAnnotation,
281
+ "an annotation must not contain a property expanding to @id" if
282
+ expanded_active_property == '@annotation' && @options[:rdfstar]
283
+
261
284
  # If expanded property is @id and value is not a string, an invalid @id value error has been detected and processing is aborted
262
285
  e_id = case value
263
286
  when String
@@ -280,7 +303,11 @@ module JSON::LD
280
303
  [{}]
281
304
  elsif @options[:rdfstar]
282
305
  # Result must have just a single statement
283
- rei_node = expand(value, active_property, context, log_depth: log_depth.to_i + 1)
306
+ rei_node = expand(value, nil, context, log_depth: log_depth.to_i + 1)
307
+
308
+ # Node must not contain @reverse
309
+ raise JsonLdError::InvalidEmbeddedNode,
310
+ "Embedded node with @reverse" if rei_node && rei_node.key?('@reverse')
284
311
  statements = to_enum(:item_to_rdf, rei_node)
285
312
  raise JsonLdError::InvalidEmbeddedNode,
286
313
  "Embedded node with #{statements.size} statements" unless
@@ -314,7 +341,7 @@ module JSON::LD
314
341
  Array(output_object['@included']) + included_result
315
342
  when '@type'
316
343
  # 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.
317
- #log_debug("@type", depth: log_depth.to_i) {"value: #{value.inspect}"}
344
+ log_debug("@type", depth: log_depth.to_i) {"value: #{value.inspect}"}
318
345
  e_type = case value
319
346
  when Array
320
347
  value.map do |v|
@@ -465,6 +492,11 @@ module JSON::LD
465
492
  # Spec FIXME: need to be sure that result is an array
466
493
  value = as_array(value)
467
494
 
495
+ # Make sure that no member of value contains an annotation object
496
+ raise JsonLdError::InvalidAnnotation,
497
+ "A list element must not contain @annotation." if
498
+ value.any? {|n| n.is_a?(Hash) && n.key?('@annotation')}
499
+
468
500
  value
469
501
  when '@set'
470
502
  # If expanded property is @set, set expanded value to the result of using this algorithm recursively, passing active context, active property, and value for element.
@@ -483,8 +515,8 @@ module JSON::LD
483
515
  log_depth: log_depth.to_i + 1)
484
516
 
485
517
  # If expanded value contains an @reverse member, i.e., properties that are reversed twice, execute for each of its property and item the following steps:
486
- if value.has_key?('@reverse')
487
- #log_debug("@reverse", depth: log_depth.to_i) {"double reverse: #{value.inspect}"}
518
+ if value.key?('@reverse')
519
+ log_debug("@reverse", depth: log_depth.to_i) {"double reverse: #{value.inspect}"}
488
520
  value['@reverse'].each do |property, item|
489
521
  # If result does not have a property member, create one and set its value to an empty array.
490
522
  # Append item to the value of the property member of result.
@@ -522,13 +554,19 @@ module JSON::LD
522
554
  nests << key
523
555
  # Continue with the next key from element
524
556
  next
557
+ when '@annotation'
558
+ # Skip unless rdfstar option is set
559
+ next unless @options[:rdfstar]
560
+ as_array(expand(value, '@annotation', context,
561
+ framing: framing,
562
+ log_depth: log_depth.to_i + 1))
525
563
  else
526
564
  # Skip unknown keyword
527
565
  next
528
566
  end
529
567
 
530
568
  # Unless expanded value is null, set the expanded property member of result to expanded value.
531
- #log_debug("expand #{expanded_property}", depth: log_depth.to_i) { expanded_value.inspect}
569
+ log_debug("expand #{expanded_property}", depth: log_depth.to_i) { expanded_value.inspect}
532
570
  output_object[expanded_property] = expanded_value unless expanded_value.nil? && expanded_property == '@value' && input_type != '@json'
533
571
  next
534
572
  end
@@ -650,21 +688,21 @@ module JSON::LD
650
688
 
651
689
  # If expanded value is null, ignore key by continuing to the next key from element.
652
690
  if expanded_value.nil?
653
- #log_debug(" => skip nil value", depth: log_depth.to_i)
691
+ log_debug(" => skip nil value", depth: log_depth.to_i)
654
692
  next
655
693
  end
656
- #log_debug(depth: log_depth.to_i) {" => #{expanded_value.inspect}"}
694
+ log_debug(depth: log_depth.to_i) {" => #{expanded_value.inspect}"}
657
695
 
658
696
  # If the container mapping associated to key in active context is @list and expanded value is not already a list object, convert expanded value to a list object by first setting it to an array containing only expanded value if it is not already an array, and then by setting it to a JSON object containing the key-value pair @list-expanded value.
659
697
  if container.first == '@list' && container.length == 1 && !list?(expanded_value)
660
- #log_debug(" => ", depth: log_depth.to_i) { "convert #{expanded_value.inspect} to list"}
698
+ log_debug(" => ", depth: log_depth.to_i) { "convert #{expanded_value.inspect} to list"}
661
699
  expanded_value = {'@list' => as_array(expanded_value)}
662
700
  end
663
- #log_debug(depth: log_depth.to_i) {" => #{expanded_value.inspect}"}
701
+ log_debug(depth: log_depth.to_i) {" => #{expanded_value.inspect}"}
664
702
 
665
703
  # convert expanded value to @graph if container specifies it
666
704
  if container.first == '@graph' && container.length == 1
667
- #log_debug(" => ", depth: log_depth.to_i) { "convert #{expanded_value.inspect} to list"}
705
+ log_debug(" => ", depth: log_depth.to_i) { "convert #{expanded_value.inspect} to list"}
668
706
  expanded_value = as_array(expanded_value).map do |v|
669
707
  {'@graph' => as_array(v)}
670
708
  end