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.
- checksums.yaml +4 -4
- data/README.md +20 -7
- data/VERSION +1 -1
- data/bin/jsonld +13 -11
- data/lib/json/ld/api.rb +34 -17
- data/lib/json/ld/compact.rb +14 -6
- data/lib/json/ld/context.rb +63 -57
- data/lib/json/ld/expand.rb +57 -19
- data/lib/json/ld/extensions.rb +4 -4
- data/lib/json/ld/flatten.rb +137 -9
- data/lib/json/ld/format.rb +8 -1
- data/lib/json/ld/frame.rb +8 -8
- data/lib/json/ld/from_rdf.rb +14 -8
- data/lib/json/ld/html/nokogiri.rb +3 -4
- data/lib/json/ld/reader.rb +1 -0
- data/lib/json/ld/streaming_reader.rb +5 -5
- data/lib/json/ld/streaming_writer.rb +4 -4
- data/lib/json/ld/to_rdf.rb +2 -2
- data/lib/json/ld/utils.rb +13 -13
- data/lib/json/ld/writer.rb +2 -2
- data/lib/json/ld.rb +3 -1
- data/spec/api_spec.rb +1 -1
- data/spec/compact_spec.rb +277 -3
- data/spec/context_spec.rb +8 -3
- data/spec/expand_spec.rb +385 -2
- data/spec/flatten_spec.rb +517 -1
- data/spec/frame_spec.rb +93 -3
- data/spec/from_rdf_spec.rb +1 -1
- data/spec/rdfstar_spec.rb +25 -0
- data/spec/spec_helper.rb +40 -2
- data/spec/suite_flatten_spec.rb +4 -0
- data/spec/suite_frame_spec.rb +7 -0
- data/spec/suite_helper.rb +13 -7
- data/spec/suite_to_rdf_spec.rb +1 -1
- data/spec/to_rdf_spec.rb +4 -4
- data/spec/writer_spec.rb +193 -0
- metadata +6 -4
data/lib/json/ld/context.rb
CHANGED
@@ -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
|
-
|
270
|
+
log_debug("parse") {"context: #{context.inspect}"}
|
270
271
|
result = result.merge(context)
|
271
272
|
when IO, StringIO
|
272
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
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.
|
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.
|
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.
|
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
|
-
|
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.
|
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.
|
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.
|
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.
|
606
|
+
elsif value.key?('@id') && value['@id'].nil?
|
604
607
|
# Allowed to reserve a null term, which may be protected
|
605
|
-
elsif value.
|
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.
|
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
|
-
|
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
|
-
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
1319
|
+
if item.key?('@direction')
|
1316
1320
|
item_language = "#{item['@language']}_#{item['@direction']}".downcase
|
1317
|
-
elsif item.
|
1321
|
+
elsif item.key?('@language')
|
1318
1322
|
item_language = item['@language'].downcase
|
1319
|
-
elsif item.
|
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.
|
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.
|
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.
|
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.
|
1370
|
+
elsif value.key?('@direction') && !index?(value)
|
1367
1371
|
tl_value = "_#{value['@direction']}"
|
1368
|
-
elsif value.
|
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.
|
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.
|
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.
|
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.
|
1539
|
+
elsif value.datatype?
|
1534
1540
|
res['@type'] = uri(value.datatype).to_s
|
1535
|
-
elsif value.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
data/lib/json/ld/expand.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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.
|
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.
|
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
|
-
|
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
|
-
|
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.
|
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,
|
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
|
-
|
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.
|
487
|
-
|
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
|
-
|
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
|
-
|
691
|
+
log_debug(" => skip nil value", depth: log_depth.to_i)
|
654
692
|
next
|
655
693
|
end
|
656
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|