json-ld 2.1.2 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +107 -4
- data/VERSION +1 -1
- data/lib/json/ld.rb +38 -33
- data/lib/json/ld/api.rb +27 -14
- data/lib/json/ld/compact.rb +65 -10
- data/lib/json/ld/context.rb +158 -22
- data/lib/json/ld/expand.rb +339 -279
- data/lib/json/ld/frame.rb +30 -5
- data/lib/json/ld/from_rdf.rb +4 -3
- data/spec/compact_spec.rb +1107 -328
- data/spec/context_spec.rb +153 -27
- data/spec/expand_spec.rb +907 -152
- data/spec/format_spec.rb +1 -1
- data/spec/frame_spec.rb +90 -24
- data/spec/spec_helper.rb +0 -6
- data/spec/streaming_writer_spec.rb +2 -2
- data/spec/suite_helper.rb +2 -3
- data/spec/writer_spec.rb +2 -2
- metadata +7 -49
data/lib/json/ld/context.rb
CHANGED
@@ -36,9 +36,12 @@ module JSON::LD
|
|
36
36
|
# @return [String] Type mapping
|
37
37
|
attr_accessor :type_mapping
|
38
38
|
|
39
|
-
# @return ['@set', '@
|
39
|
+
# @return ['@index', '@language', '@index', '@set', '@type', '@id'] Container mapping
|
40
40
|
attr_accessor :container_mapping
|
41
41
|
|
42
|
+
# @return [String] Term used for nest properties
|
43
|
+
attr_accessor :nest
|
44
|
+
|
42
45
|
# Language mapping of term, `false` is used if there is explicitly no language mapping for this term.
|
43
46
|
# @return [String] Language mapping
|
44
47
|
attr_accessor :language_mapping
|
@@ -47,9 +50,13 @@ module JSON::LD
|
|
47
50
|
attr_accessor :reverse_property
|
48
51
|
|
49
52
|
# This is a simple term definition, not an expanded term definition
|
50
|
-
# @return [Boolean]
|
53
|
+
# @return [Boolean]
|
51
54
|
attr_accessor :simple
|
52
55
|
|
56
|
+
# Term-specific context
|
57
|
+
# @return [Hash{String => Object}]
|
58
|
+
attr_accessor :context
|
59
|
+
|
53
60
|
# This is a simple term definition, not an expanded term definition
|
54
61
|
# @return [Boolean] simple
|
55
62
|
def simple?; simple; end
|
@@ -58,10 +65,11 @@ module JSON::LD
|
|
58
65
|
# @param [String] term
|
59
66
|
# @param [String] id
|
60
67
|
# @param [String] type_mapping Type mapping
|
61
|
-
# @param ['@set', '@
|
68
|
+
# @param ['@index', '@language', '@index', '@set', '@type', '@id'] container_mapping
|
62
69
|
# @param [String] language_mapping
|
63
70
|
# Language mapping of term, `false` is used if there is explicitly no language mapping for this term
|
64
71
|
# @param [Boolean] reverse_property
|
72
|
+
# @param [String] nest term used for nest properties
|
65
73
|
# @param [Boolean] simple
|
66
74
|
# This is a simple term definition, not an expanded term definition
|
67
75
|
def initialize(term,
|
@@ -70,14 +78,18 @@ module JSON::LD
|
|
70
78
|
container_mapping: nil,
|
71
79
|
language_mapping: nil,
|
72
80
|
reverse_property: false,
|
73
|
-
|
81
|
+
nest: nil,
|
82
|
+
simple: false,
|
83
|
+
context: nil)
|
74
84
|
@term = term
|
75
85
|
@id = id.to_s if id
|
76
86
|
@type_mapping = type_mapping.to_s if type_mapping
|
77
87
|
@container_mapping = container_mapping if container_mapping
|
78
88
|
@language_mapping = language_mapping if language_mapping
|
79
89
|
@reverse_property = reverse_property if reverse_property
|
90
|
+
@nest = nest if nest
|
80
91
|
@simple = simple if simple
|
92
|
+
@context = context if context
|
81
93
|
end
|
82
94
|
|
83
95
|
##
|
@@ -98,7 +110,9 @@ module JSON::LD
|
|
98
110
|
if language_mapping.nil? &&
|
99
111
|
container_mapping.nil? &&
|
100
112
|
type_mapping.nil? &&
|
101
|
-
reverse_property.nil?
|
113
|
+
reverse_property.nil? &&
|
114
|
+
self.context.nil? &&
|
115
|
+
nest.nil?
|
102
116
|
|
103
117
|
cid.to_s unless cid == term && context.vocab
|
104
118
|
else
|
@@ -114,16 +128,19 @@ module JSON::LD
|
|
114
128
|
defn['@container'] = container_mapping if container_mapping
|
115
129
|
# Language set as false to be output as null
|
116
130
|
defn['@language'] = (language_mapping ? language_mapping : nil) unless language_mapping.nil?
|
131
|
+
defn['@context'] = self.context unless self.context.nil?
|
132
|
+
defn['@nest'] = selfnest unless self.nest.nil?
|
117
133
|
defn
|
118
134
|
end
|
119
135
|
end
|
120
136
|
|
121
137
|
##
|
122
138
|
# Turn this into a source for a new instantiation
|
139
|
+
# FIXME: context serialization
|
123
140
|
# @return [String]
|
124
141
|
def to_rb
|
125
142
|
defn = [%(TermDefinition.new\(#{term.inspect})]
|
126
|
-
%w(id type_mapping container_mapping language_mapping reverse_property simple).each do |acc|
|
143
|
+
%w(id type_mapping container_mapping language_mapping reverse_property nest simple context).each do |acc|
|
127
144
|
v = instance_variable_get("@#{acc}".to_sym)
|
128
145
|
v = v.to_s if v.is_a?(RDF::Term)
|
129
146
|
defn << "#{acc}: #{v.inspect}" if v
|
@@ -139,6 +156,8 @@ module JSON::LD
|
|
139
156
|
v << "container=#{container_mapping}" if container_mapping
|
140
157
|
v << "lang=#{language_mapping.inspect}" unless language_mapping.nil?
|
141
158
|
v << "type=#{type_mapping}" unless type_mapping.nil?
|
159
|
+
v << "nest=#{nest.inspect}" unless nest.nil?
|
160
|
+
v << "has-context" unless context.nil?
|
142
161
|
v.join(" ") + "]"
|
143
162
|
end
|
144
163
|
end
|
@@ -186,6 +205,9 @@ module JSON::LD
|
|
186
205
|
# @return [BlankNodeNamer]
|
187
206
|
attr_accessor :namer
|
188
207
|
|
208
|
+
# @return [String]
|
209
|
+
attr_accessor :processingMode
|
210
|
+
|
189
211
|
##
|
190
212
|
# Create a new context by parsing a context.
|
191
213
|
#
|
@@ -223,6 +245,7 @@ module JSON::LD
|
|
223
245
|
@doc_base.canonicalize! if options[:canonicalize]
|
224
246
|
end
|
225
247
|
options[:documentLoader] ||= JSON::LD::API.method(:documentLoader)
|
248
|
+
@processingMode ||= options[:processingMode]
|
226
249
|
@term_definitions = {}
|
227
250
|
@iri_to_term = {
|
228
251
|
RDF.to_uri.to_s => "rdf",
|
@@ -283,6 +306,22 @@ module JSON::LD
|
|
283
306
|
end
|
284
307
|
end
|
285
308
|
|
309
|
+
# If contex has a @version member, it's value MUST be 1.1, otherwise an "invalid @version value" has been detected, and processing is aborted.
|
310
|
+
# If processingMode has been set, and "json-ld-1.1" is not a prefix of processingMode , a "processing mode conflict" has been detecting, and processing is aborted.
|
311
|
+
# @param [Number] vaule must be a decimal number
|
312
|
+
def version=(value)
|
313
|
+
case value
|
314
|
+
when 1.1
|
315
|
+
if processingMode && !processingMode.start_with?("json-ld-1.1")
|
316
|
+
raise JsonLdError::ProcessingModeConflict, "#{value} not compatible with #{processingMode}"
|
317
|
+
end
|
318
|
+
@processingMode ||= "json-ld-1.1"
|
319
|
+
else
|
320
|
+
raise JsonLdError::InvalidVersionValue, value
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# 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.
|
286
325
|
# @param [String] value must be an absolute IRI
|
287
326
|
def vocab=(value)
|
288
327
|
@vocab = case value
|
@@ -380,18 +419,18 @@ module JSON::LD
|
|
380
419
|
end
|
381
420
|
raise JsonLdError::InvalidRemoteContext, "#{context}" unless jo.is_a?(Hash) && jo.has_key?('@context')
|
382
421
|
context = jo['@context']
|
383
|
-
if
|
422
|
+
if processingMode && processingMode <= "json-ld-1.1"
|
384
423
|
context_no_base.provided_context = context.dup
|
385
424
|
end
|
386
425
|
end
|
387
426
|
rescue JsonLdError::LoadingDocumentFailed => e
|
388
427
|
#log_debug("parse") {"Failed to retrieve @context from remote document at #{context_no_base.context_base.inspect}: #{e.message}"}
|
389
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{context_no_base.context_base}", e.backtrace
|
428
|
+
raise JsonLdError::LoadingRemoteContextFailed, "#{context_no_base.context_base}: #{e.message}", e.backtrace
|
390
429
|
rescue JsonLdError
|
391
430
|
raise
|
392
|
-
rescue
|
431
|
+
rescue StandardError => e
|
393
432
|
#log_debug("parse") {"Failed to retrieve @context from remote document at #{context_no_base.context_base.inspect}: #{e.message}"}
|
394
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{context_no_base.context_base}", e.backtrace
|
433
|
+
raise JsonLdError::LoadingRemoteContextFailed, "#{context_no_base.context_base}: #{e.message}", e.backtrace
|
395
434
|
end
|
396
435
|
|
397
436
|
# 3.2.6) Set context to the result of recursively calling this algorithm, passing context no base for active context, context for local context, and remote contexts.
|
@@ -402,11 +441,12 @@ module JSON::LD
|
|
402
441
|
result = context
|
403
442
|
#log_debug("parse") {"=> provided_context: #{context.inspect}"}
|
404
443
|
when Hash
|
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.
|
406
444
|
context = context.dup # keep from modifying a hash passed as a param
|
445
|
+
|
407
446
|
{
|
408
447
|
'@base' => :base=,
|
409
448
|
'@language' => :default_language=,
|
449
|
+
'@version' => :version=,
|
410
450
|
'@vocab' => :vocab=,
|
411
451
|
}.each do |key, setter|
|
412
452
|
v = context.fetch(key, false)
|
@@ -417,6 +457,9 @@ module JSON::LD
|
|
417
457
|
end
|
418
458
|
end
|
419
459
|
|
460
|
+
# If not set explicitly, set processingMode to "json-ld-1.0"
|
461
|
+
result.processingMode ||= "json-ld-1.0"
|
462
|
+
|
420
463
|
defined = {}
|
421
464
|
# For each key-value pair in context invoke the Create Term Definition subalgorithm, passing result for active context, context for local context, key, and defined
|
422
465
|
context.each_key do |key|
|
@@ -454,6 +497,7 @@ module JSON::LD
|
|
454
497
|
|
455
498
|
# Merge in Term Definitions
|
456
499
|
term_definitions.merge!(context.term_definitions)
|
500
|
+
@inverse_context = nil # Re-build after term definitions set
|
457
501
|
self
|
458
502
|
end
|
459
503
|
|
@@ -484,7 +528,7 @@ module JSON::LD
|
|
484
528
|
end
|
485
529
|
|
486
530
|
# Since keywords cannot be overridden, term must not be a keyword. Otherwise, an invalid value has been detected, which is an error.
|
487
|
-
if KEYWORDS.include?(term) && !%w(@vocab @language).include?(term)
|
531
|
+
if KEYWORDS.include?(term) && !%w(@vocab @language @version).include?(term)
|
488
532
|
raise JsonLdError::KeywordRedefinition, "term must not be a keyword: #{term.inspect}" if
|
489
533
|
@options[:validate]
|
490
534
|
elsif !term_valid?(term) && @options[:validate]
|
@@ -511,6 +555,16 @@ module JSON::LD
|
|
511
555
|
definition = TermDefinition.new(term)
|
512
556
|
definition.simple = simple_term
|
513
557
|
|
558
|
+
expected_keys = case @options[:processingMode]
|
559
|
+
when "json-ld-1.0" then %w(@id @reverse @type @container @language)
|
560
|
+
else %w(@id @reverse @type @container @language @context @nest)
|
561
|
+
end
|
562
|
+
|
563
|
+
extra_keys = value.keys - expected_keys
|
564
|
+
if !extra_keys.empty? && @options[:validate]
|
565
|
+
raise JsonLdError::InvalidTermDefinition, "Term definition for #{term.inspect} has unexpected keys: #{extra_keys.join(', ')}"
|
566
|
+
end
|
567
|
+
|
514
568
|
if value.has_key?('@type')
|
515
569
|
type = value['@type']
|
516
570
|
# SPEC FIXME: @type may be nil
|
@@ -535,7 +589,7 @@ module JSON::LD
|
|
535
589
|
|
536
590
|
if value.has_key?('@reverse')
|
537
591
|
raise JsonLdError::InvalidReverseProperty, "unexpected key in #{value.inspect} on term #{term.inspect}" if
|
538
|
-
value.keys.any? {|k| %w(@id).include?(k)}
|
592
|
+
value.keys.any? {|k| %w(@id @nest).include?(k)}
|
539
593
|
raise JsonLdError::InvalidIRIMapping, "expected value of @reverse to be a string: #{value['@reverse'].inspect} on term #{term.inspect}" unless
|
540
594
|
value['@reverse'].is_a?(String)
|
541
595
|
|
@@ -548,12 +602,13 @@ module JSON::LD
|
|
548
602
|
raise JsonLdError::InvalidIRIMapping, "non-absolute @reverse IRI: #{definition.id} on term #{term.inspect}" unless
|
549
603
|
definition.id.is_a?(RDF::URI) && definition.id.absolute?
|
550
604
|
|
551
|
-
# If value contains an @container member, set the container mapping of definition to its value; if its value is neither @set,
|
552
|
-
if
|
605
|
+
# 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.
|
606
|
+
if value.has_key?('@container')
|
607
|
+
container = value['@container']
|
553
608
|
raise JsonLdError::InvalidReverseProperty,
|
554
609
|
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" unless
|
555
|
-
['@set', '@index'
|
556
|
-
definition.container_mapping = container
|
610
|
+
['@set', '@index'].include?(container)
|
611
|
+
definition.container_mapping = check_container(container, local_context, defined, term)
|
557
612
|
end
|
558
613
|
definition.reverse_property = true
|
559
614
|
elsif value.has_key?('@id') && value['@id'] != term
|
@@ -589,10 +644,20 @@ module JSON::LD
|
|
589
644
|
@iri_to_term[definition.id] = term if simple_term && definition.id
|
590
645
|
|
591
646
|
if value.has_key?('@container')
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
647
|
+
#log_debug("") {"container_mapping: #{value['@container'].inspect}"}
|
648
|
+
definition.container_mapping = check_container(value['@container'], local_context, defined, term)
|
649
|
+
end
|
650
|
+
|
651
|
+
if value.has_key?('@context')
|
652
|
+
# Not supported in JSON-LD 1.0
|
653
|
+
raise JsonLdError::InvalidTermDefinition, '@context not valid in term definition for JSON-LD 1.0' if processingMode < 'json-ld-1.1'
|
654
|
+
|
655
|
+
begin
|
656
|
+
self.parse(value['@context'])
|
657
|
+
definition.context = value['@context']
|
658
|
+
rescue JsonLdError => e
|
659
|
+
raise JsonLdError::InvalidScopedContext, "Term definition for #{term.inspect} contains illegal value for @context: #{e.message}"
|
660
|
+
end
|
596
661
|
end
|
597
662
|
|
598
663
|
if value.has_key?('@language')
|
@@ -603,11 +668,28 @@ module JSON::LD
|
|
603
668
|
definition.language_mapping = language || false
|
604
669
|
end
|
605
670
|
|
671
|
+
if value.has_key?('@nest')
|
672
|
+
# Not supported in JSON-LD 1.0
|
673
|
+
raise JsonLdError::InvalidTermDefinition, '@nest not valid in term definition for JSON-LD 1.0' if processingMode < 'json-ld-1.1'
|
674
|
+
|
675
|
+
# Not supported in JSON-LD 1.0
|
676
|
+
raise JsonLdError::InvalidTermDefinition, '@nest not valid in term definition for JSON-LD 1.0' if processingMode < 'json-ld-1.1'
|
677
|
+
|
678
|
+
nest = value['@nest']
|
679
|
+
raise JsonLdError::InvalidNestValue, "nest must be a string, was #{nest.inspect}} on term #{term.inspect}" unless nest.is_a?(String)
|
680
|
+
raise JsonLdError::InvalidNestValue, "nest must not be a keyword other than @nest, was #{nest.inspect}} on term #{term.inspect}" if nest.start_with?('@') && nest != '@nest'
|
681
|
+
#log_debug("") {"nest: #{nest.inspect}"}
|
682
|
+
definition.nest = nest
|
683
|
+
end
|
684
|
+
|
606
685
|
term_definitions[term] = definition
|
607
686
|
defined[term] = true
|
608
687
|
else
|
609
688
|
raise JsonLdError::InvalidTermDefinition, "Term definition for #{term.inspect} is an #{value.class} on term #{term.inspect}"
|
610
689
|
end
|
690
|
+
ensure
|
691
|
+
# Re-build after term definitions set
|
692
|
+
@inverse_context = nil
|
611
693
|
end
|
612
694
|
|
613
695
|
##
|
@@ -766,6 +848,37 @@ module JSON::LD
|
|
766
848
|
term && term.container_mapping
|
767
849
|
end
|
768
850
|
|
851
|
+
##
|
852
|
+
# Retrieve content of a term
|
853
|
+
#
|
854
|
+
# @param [Term, #to_s] term in unexpanded form
|
855
|
+
# @return [Hash]
|
856
|
+
def content(term)
|
857
|
+
term = find_definition(term)
|
858
|
+
term && term.content
|
859
|
+
end
|
860
|
+
|
861
|
+
##
|
862
|
+
# Retrieve nest of a term.
|
863
|
+
# value of nest must be @nest or a term that resolves to @nest
|
864
|
+
#
|
865
|
+
# @param [Term, #to_s] term in unexpanded form
|
866
|
+
# @return [String] Nesting term
|
867
|
+
# @raise JsonLdError::InvalidNestValue if nesting term exists and is not a term resolving to `@nest` in the current context.
|
868
|
+
def nest(term)
|
869
|
+
term = find_definition(term)
|
870
|
+
if term
|
871
|
+
case term.nest
|
872
|
+
when '@nest', nil
|
873
|
+
term.nest
|
874
|
+
else
|
875
|
+
nest_term = find_definition(term.nest)
|
876
|
+
raise JsonLdError::InvalidNestValue, "nest must a term resolving to @nest" unless nest_term && nest_term.simple? && nest_term.id == '@nest'
|
877
|
+
term.nest
|
878
|
+
end
|
879
|
+
end
|
880
|
+
end
|
881
|
+
|
769
882
|
##
|
770
883
|
# Retrieve the language associated with a term, or the default language otherwise
|
771
884
|
# @param [Term, #to_s] term in unexpanded form
|
@@ -909,7 +1022,12 @@ module JSON::LD
|
|
909
1022
|
default_language = self.default_language || @none
|
910
1023
|
containers = []
|
911
1024
|
tl, tl_value = "@language", "@null"
|
912
|
-
|
1025
|
+
|
1026
|
+
# If the value is a JSON Object, then for the keywords @index, @id, and @type, if the value contains that keyword, append it to containers.
|
1027
|
+
%w(@index @id @type).each do |kw|
|
1028
|
+
containers << kw if value.has_key?(kw)
|
1029
|
+
end if value.is_a?(Hash)
|
1030
|
+
|
913
1031
|
if reverse
|
914
1032
|
tl, tl_value = "@type", "@reverse"
|
915
1033
|
containers << '@set'
|
@@ -1220,6 +1338,7 @@ module JSON::LD
|
|
1220
1338
|
v = %w([Context)
|
1221
1339
|
v << "base=#{base}" if base
|
1222
1340
|
v << "vocab=#{vocab}" if vocab
|
1341
|
+
v << "processingMode=#{processingMode}" if processingMode
|
1223
1342
|
v << "default_language=#{default_language}" if default_language
|
1224
1343
|
v << "term_definitions[#{term_definitions.length}]=#{term_definitions}"
|
1225
1344
|
v.join(" ") + "]"
|
@@ -1438,5 +1557,22 @@ module JSON::LD
|
|
1438
1557
|
memo
|
1439
1558
|
end
|
1440
1559
|
end
|
1560
|
+
|
1561
|
+
# Ensure @container mapping is appropriate
|
1562
|
+
# The result is the original container definition. For IRI containers, this is necessary to be able to determine the @type mapping for string values
|
1563
|
+
def check_container(container, local_context, defined, term)
|
1564
|
+
case container
|
1565
|
+
when '@set', '@list', '@language', '@index', nil
|
1566
|
+
# Okay
|
1567
|
+
when '@type', '@id', nil
|
1568
|
+
raise JsonLdError::InvalidContainerMapping,
|
1569
|
+
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" if
|
1570
|
+
processingMode < 'json-ld-1.1'
|
1571
|
+
else
|
1572
|
+
raise JsonLdError::InvalidContainerMapping,
|
1573
|
+
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}"
|
1574
|
+
end
|
1575
|
+
container
|
1576
|
+
end
|
1441
1577
|
end
|
1442
1578
|
end
|
data/lib/json/ld/expand.rb
CHANGED
@@ -42,290 +42,21 @@ module JSON::LD
|
|
42
42
|
end
|
43
43
|
|
44
44
|
output_object = {}
|
45
|
-
# Then, proceed and process each property and value in element as follows:
|
46
45
|
keys = ordered ? input.keys.kw_sort : input.keys
|
47
|
-
keys.each do |key|
|
48
|
-
# For each key and value in element, ordered lexicographically by key:
|
49
|
-
value = input[key]
|
50
|
-
expanded_property = context.expand_iri(key, vocab: true, log_depth: @options[:log_depth])
|
51
46
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
#log_debug(" => ") {"skip nil property"}
|
60
|
-
next
|
61
|
-
end
|
62
|
-
|
63
|
-
if KEYWORDS.include?(expanded_property)
|
64
|
-
# If active property equals @reverse, an invalid reverse property map error has been detected and processing is aborted.
|
65
|
-
raise JsonLdError::InvalidReversePropertyMap,
|
66
|
-
"@reverse not appropriate at this point" if active_property == '@reverse'
|
67
|
-
|
68
|
-
# If result has already an expanded property member, an colliding keywords error has been detected and processing is aborted.
|
69
|
-
raise JsonLdError::CollidingKeywords,
|
70
|
-
"#{expanded_property} already exists in result" if output_object.has_key?(expanded_property)
|
71
|
-
|
72
|
-
expanded_value = case expanded_property
|
73
|
-
when '@id'
|
74
|
-
# If expanded property is @id and value is not a string, an invalid @id value error has been detected and processing is aborted
|
75
|
-
e_id = case value
|
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
|
88
|
-
when Hash
|
89
|
-
raise JsonLdError::InvalidIdValue,
|
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
|
-
[{}]
|
95
|
-
else
|
96
|
-
raise JsonLdError::InvalidIdValue,
|
97
|
-
"value of @id must be a string or hash if framing: #{value.inspect}"
|
98
|
-
end
|
99
|
-
|
100
|
-
# Use array form if framing
|
101
|
-
if framing && !e_id.is_a?(Array)
|
102
|
-
[e_id]
|
103
|
-
else
|
104
|
-
e_id
|
105
|
-
end
|
106
|
-
when '@type'
|
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.
|
108
|
-
#log_debug("@type") {"value: #{value.inspect}"}
|
109
|
-
case value
|
110
|
-
when Array
|
111
|
-
value.map do |v|
|
112
|
-
raise JsonLdError::InvalidTypeValue,
|
113
|
-
"@type value must be a string or array of strings: #{v.inspect}" unless v.is_a?(String)
|
114
|
-
context.expand_iri(v, vocab: true, documentRelative: true, quiet: true, log_depth: @options[:log_depth]).to_s
|
115
|
-
end
|
116
|
-
when String
|
117
|
-
context.expand_iri(value, vocab: true, documentRelative: true, quiet: true, log_depth: @options[:log_depth]).to_s
|
118
|
-
when Hash
|
119
|
-
# For framing
|
120
|
-
raise JsonLdError::InvalidTypeValue,
|
121
|
-
"@type value must be a an empty object for framing: #{value.inspect}" unless
|
122
|
-
value.empty? && framing
|
123
|
-
[{}]
|
124
|
-
else
|
125
|
-
raise JsonLdError::InvalidTypeValue,
|
126
|
-
"@type value must be a string or array of strings: #{value.inspect}"
|
127
|
-
end
|
128
|
-
when '@graph'
|
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.
|
130
|
-
value = expand(value, '@graph', context, ordered: ordered)
|
131
|
-
value.is_a?(Array) ? value : [value]
|
132
|
-
when '@value'
|
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.
|
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
|
138
|
-
output_object['@value'] = nil
|
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}"
|
152
|
-
end
|
153
|
-
when '@language'
|
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.
|
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
|
171
|
-
when '@index'
|
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.
|
173
|
-
raise JsonLdError::InvalidIndexValue,
|
174
|
-
"Value of @index is not a string: #{value.inspect}" unless value.is_a?(String)
|
175
|
-
value
|
176
|
-
when '@list'
|
177
|
-
# If expanded property is @list:
|
178
|
-
|
179
|
-
# If active property is null or @graph, continue with the next key from element to remove the free-floating list.
|
180
|
-
next if (active_property || '@graph') == '@graph'
|
181
|
-
|
182
|
-
# Otherwise, initialize expanded value to the result of using this algorithm recursively passing active context, active property, and value for element.
|
183
|
-
value = expand(value, active_property, context, ordered: ordered)
|
184
|
-
|
185
|
-
# Spec FIXME: need to be sure that result is an array
|
186
|
-
value = [value] unless value.is_a?(Array)
|
187
|
-
|
188
|
-
# If expanded value is a list object, a list of lists error has been detected and processing is aborted.
|
189
|
-
# Spec FIXME: Also look at each object if result is an array
|
190
|
-
raise JsonLdError::ListOfLists,
|
191
|
-
"A list may not contain another list" if value.any? {|v| list?(v)}
|
192
|
-
|
193
|
-
value
|
194
|
-
when '@set'
|
195
|
-
# 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.
|
196
|
-
expand(value, active_property, context, ordered: ordered)
|
197
|
-
when '@reverse'
|
198
|
-
# If expanded property is @reverse and value is not a JSON object, an invalid @reverse value error has been detected and processing is aborted.
|
199
|
-
raise JsonLdError::InvalidReverseValue,
|
200
|
-
"@reverse value must be an object: #{value.inspect}" unless value.is_a?(Hash)
|
201
|
-
|
202
|
-
# Otherwise
|
203
|
-
# Initialize expanded value to the result of using this algorithm recursively, passing active context, @reverse as active property, and value as element.
|
204
|
-
value = expand(value, '@reverse', context, ordered: ordered)
|
205
|
-
|
206
|
-
# 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:
|
207
|
-
if value.has_key?('@reverse')
|
208
|
-
#log_debug("@reverse") {"double reverse: #{value.inspect}"}
|
209
|
-
value['@reverse'].each do |property, item|
|
210
|
-
# If result does not have a property member, create one and set its value to an empty array.
|
211
|
-
# Append item to the value of the property member of result.
|
212
|
-
(output_object[property] ||= []).concat([item].flatten.compact)
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
# If expanded value contains members other than @reverse:
|
217
|
-
unless value.keys.reject {|k| k == '@reverse'}.empty?
|
218
|
-
# If result does not have an @reverse member, create one and set its value to an empty JSON object.
|
219
|
-
reverse_map = output_object['@reverse'] ||= {}
|
220
|
-
value.each do |property, items|
|
221
|
-
next if property == '@reverse'
|
222
|
-
items.each do |item|
|
223
|
-
if value?(item) || list?(item)
|
224
|
-
raise JsonLdError::InvalidReversePropertyValue,
|
225
|
-
item.inspect
|
226
|
-
end
|
227
|
-
merge_value(reverse_map, property, item)
|
228
|
-
end
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
# Continue with the next key from element
|
233
|
-
next
|
234
|
-
when '@default', '@embed', '@explicit', '@omitDefault', '@preserve', '@requireAll'
|
235
|
-
next unless framing
|
236
|
-
# Framing keywords
|
237
|
-
[expand(value, expanded_property, context, ordered: ordered)].flatten
|
238
|
-
else
|
239
|
-
# Skip unknown keyword
|
240
|
-
next
|
241
|
-
end
|
242
|
-
|
243
|
-
# Unless expanded value is null, set the expanded property member of result to expanded value.
|
244
|
-
#log_debug("expand #{expanded_property}") { expanded_value.inspect}
|
245
|
-
output_object[expanded_property] = expanded_value unless expanded_value.nil?
|
246
|
-
next
|
247
|
-
end
|
248
|
-
|
249
|
-
expanded_value = if context.container(key) == '@language' && value.is_a?(Hash)
|
250
|
-
# Otherwise, if key's container mapping in active context is @language and value is a JSON object then value is expanded from a language map as follows:
|
251
|
-
|
252
|
-
# Set multilingual array to an empty array.
|
253
|
-
ary = []
|
254
|
-
|
255
|
-
# For each key-value pair language-language value in value, ordered lexicographically by language
|
256
|
-
keys = ordered ? value.keys.sort : value.keys
|
257
|
-
keys.each do |k|
|
258
|
-
[value[k]].flatten.each do |item|
|
259
|
-
# item must be a string, otherwise an invalid language map value error has been detected and processing is aborted.
|
260
|
-
raise JsonLdError::InvalidLanguageMapValue,
|
261
|
-
"Expected #{item.inspect} to be a string" unless item.is_a?(String)
|
262
|
-
|
263
|
-
# Append a JSON object to expanded value that consists of two key-value pairs: (@value-item) and (@language-lowercased language).
|
264
|
-
ary << {
|
265
|
-
'@value' => item,
|
266
|
-
'@language' => k.downcase
|
267
|
-
}
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
ary
|
272
|
-
elsif context.container(key) == '@index' && value.is_a?(Hash)
|
273
|
-
# Otherwise, if key's container mapping in active context is @index and value is a JSON object then value is expanded from an index map as follows:
|
274
|
-
|
275
|
-
# Set ary to an empty array.
|
276
|
-
ary = []
|
277
|
-
|
278
|
-
# For each key-value in the object:
|
279
|
-
keys = ordered ? value.keys.sort : value.keys
|
280
|
-
keys.each do |k|
|
281
|
-
# Initialize index value to the result of using this algorithm recursively, passing active context, key as active property, and index value as element.
|
282
|
-
index_value = expand([value[k]].flatten, key, context, ordered: ordered)
|
283
|
-
index_value.each do |item|
|
284
|
-
item['@index'] ||= k
|
285
|
-
ary << item
|
286
|
-
end
|
287
|
-
end
|
288
|
-
ary
|
289
|
-
else
|
290
|
-
# Otherwise, initialize expanded value to the result of using this algorithm recursively, passing active context, key for active property, and value for element.
|
291
|
-
expand(value, key, context, ordered: ordered)
|
292
|
-
end
|
293
|
-
|
294
|
-
# If expanded value is null, ignore key by continuing to the next key from element.
|
295
|
-
if expanded_value.nil?
|
296
|
-
#log_debug(" => skip nil value")
|
297
|
-
next
|
298
|
-
end
|
299
|
-
#log_debug {" => #{expanded_value.inspect}"}
|
300
|
-
|
301
|
-
# 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.
|
302
|
-
if context.container(key) == '@list' && !list?(expanded_value)
|
303
|
-
#log_debug(" => ") { "convert #{expanded_value.inspect} to list"}
|
304
|
-
expanded_value = {'@list' => [expanded_value].flatten}
|
305
|
-
end
|
306
|
-
#log_debug {" => #{expanded_value.inspect}"}
|
307
|
-
|
308
|
-
# Otherwise, if the term definition associated to key indicates that it is a reverse property
|
309
|
-
# Spec FIXME: this is not an otherwise.
|
310
|
-
if (td = context.term_definitions[key]) && td.reverse_property
|
311
|
-
# If result has no @reverse member, create one and initialize its value to an empty JSON object.
|
312
|
-
reverse_map = output_object['@reverse'] ||= {}
|
313
|
-
[expanded_value].flatten.each do |item|
|
314
|
-
# If item is a value object or list object, an invalid reverse property value has been detected and processing is aborted.
|
315
|
-
raise JsonLdError::InvalidReversePropertyValue,
|
316
|
-
item.inspect if value?(item) || list?(item)
|
317
|
-
|
318
|
-
# If reverse map has no expanded property member, create one and initialize its value to an empty array.
|
319
|
-
# Append item to the value of the expanded property member of reverse map.
|
320
|
-
merge_value(reverse_map, expanded_property, item)
|
321
|
-
end
|
322
|
-
else
|
323
|
-
# Otherwise, if key is not a reverse property:
|
324
|
-
# If result does not have an expanded property member, create one and initialize its value to an empty array.
|
325
|
-
(output_object[expanded_property] ||= []).concat([expanded_value].flatten)
|
47
|
+
# See if keys mapping to @type have terms with a local context
|
48
|
+
input.keys.select do |key|
|
49
|
+
context.expand_iri(key, vocab: true) == '@type'
|
50
|
+
end.each do |key|
|
51
|
+
Array(input[key]).each do |term|
|
52
|
+
term_context = context.term_definitions[term].context if context.term_definitions[term]
|
53
|
+
context = term_context ? context.parse(term_context) : context
|
326
54
|
end
|
327
55
|
end
|
328
56
|
|
57
|
+
# Process each key and value in element. Ignores @nesting content
|
58
|
+
expand_object(input, active_property, context, output_object, ordered: ordered)
|
59
|
+
|
329
60
|
#log_debug("output object") {output_object.inspect}
|
330
61
|
|
331
62
|
# If result contains the key @value:
|
@@ -394,5 +125,334 @@ module JSON::LD
|
|
394
125
|
#log_debug {" => #{result.inspect}"}
|
395
126
|
result
|
396
127
|
end
|
128
|
+
|
129
|
+
private
|
130
|
+
# Expand each key and value of element adding them to result
|
131
|
+
def expand_object(input, active_property, context, output_object, ordered: false)
|
132
|
+
framing = @options[:processingMode].include?("expand-frame")
|
133
|
+
nests = []
|
134
|
+
|
135
|
+
# Then, proceed and process each property and value in element as follows:
|
136
|
+
keys = ordered ? input.keys.kw_sort : input.keys
|
137
|
+
keys.each do |key|
|
138
|
+
# For each key and value in element, ordered lexicographically by key:
|
139
|
+
value = input[key]
|
140
|
+
expanded_property = context.expand_iri(key, vocab: true, quiet: true)
|
141
|
+
|
142
|
+
# If expanded property is null or it neither contains a colon (:) nor it is a keyword, drop key by continuing to the next key.
|
143
|
+
next if expanded_property.is_a?(RDF::URI) && expanded_property.relative?
|
144
|
+
expanded_property = expanded_property.to_s if expanded_property.is_a?(RDF::Resource)
|
145
|
+
|
146
|
+
#log_debug("expand property") {"ap: #{active_property.inspect}, expanded: #{expanded_property.inspect}, value: #{value.inspect}"}
|
147
|
+
|
148
|
+
if expanded_property.nil?
|
149
|
+
#log_debug(" => ") {"skip nil property"}
|
150
|
+
next
|
151
|
+
end
|
152
|
+
|
153
|
+
if KEYWORDS.include?(expanded_property)
|
154
|
+
# If active property equals @reverse, an invalid reverse property map error has been detected and processing is aborted.
|
155
|
+
raise JsonLdError::InvalidReversePropertyMap,
|
156
|
+
"@reverse not appropriate at this point" if active_property == '@reverse'
|
157
|
+
|
158
|
+
# If result has already an expanded property member, an colliding keywords error has been detected and processing is aborted.
|
159
|
+
raise JsonLdError::CollidingKeywords,
|
160
|
+
"#{expanded_property} already exists in result" if output_object.has_key?(expanded_property)
|
161
|
+
|
162
|
+
expanded_value = case expanded_property
|
163
|
+
when '@id'
|
164
|
+
# If expanded property is @id and value is not a string, an invalid @id value error has been detected and processing is aborted
|
165
|
+
e_id = case value
|
166
|
+
when String
|
167
|
+
context.expand_iri(value, documentRelative: true, quiet: true).to_s
|
168
|
+
when Array
|
169
|
+
# Framing allows an array of IRIs, and always puts values in an array
|
170
|
+
raise JsonLdError::InvalidIdValue,
|
171
|
+
"value of @id must be a string, array of string or hash if framing: #{value.inspect}" unless framing
|
172
|
+
context.expand_iri(value, documentRelative: true, quiet: true).to_s
|
173
|
+
value.map do |v|
|
174
|
+
raise JsonLdError::InvalidTypeValue,
|
175
|
+
"@id value must be a string or array of strings for framing: #{v.inspect}" unless v.is_a?(String)
|
176
|
+
context.expand_iri(v, documentRelative: true, quiet: true,).to_s
|
177
|
+
end
|
178
|
+
when Hash
|
179
|
+
raise JsonLdError::InvalidIdValue,
|
180
|
+
"value of @id must be a string unless framing: #{value.inspect}" unless framing
|
181
|
+
raise JsonLdError::InvalidTypeValue,
|
182
|
+
"value of @id must be a an empty object for framing: #{value.inspect}" unless
|
183
|
+
value.empty? && framing
|
184
|
+
[{}]
|
185
|
+
else
|
186
|
+
raise JsonLdError::InvalidIdValue,
|
187
|
+
"value of @id must be a string or hash if framing: #{value.inspect}"
|
188
|
+
end
|
189
|
+
|
190
|
+
# Use array form if framing
|
191
|
+
if framing && !e_id.is_a?(Array)
|
192
|
+
[e_id]
|
193
|
+
else
|
194
|
+
e_id
|
195
|
+
end
|
196
|
+
when '@type'
|
197
|
+
# 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.
|
198
|
+
#log_debug("@type") {"value: #{value.inspect}"}
|
199
|
+
case value
|
200
|
+
when Array
|
201
|
+
value.map do |v|
|
202
|
+
raise JsonLdError::InvalidTypeValue,
|
203
|
+
"@type value must be a string or array of strings: #{v.inspect}" unless v.is_a?(String)
|
204
|
+
context.expand_iri(v, vocab: true, documentRelative: true, quiet: true).to_s
|
205
|
+
end
|
206
|
+
when String
|
207
|
+
context.expand_iri(value, vocab: true, documentRelative: true, quiet: true).to_s
|
208
|
+
when Hash
|
209
|
+
# For framing
|
210
|
+
raise JsonLdError::InvalidTypeValue,
|
211
|
+
"@type value must be a an empty object for framing: #{value.inspect}" unless
|
212
|
+
value.empty? && framing
|
213
|
+
[{}]
|
214
|
+
else
|
215
|
+
raise JsonLdError::InvalidTypeValue,
|
216
|
+
"@type value must be a string or array of strings: #{value.inspect}"
|
217
|
+
end
|
218
|
+
when '@graph'
|
219
|
+
# 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.
|
220
|
+
value = expand(value, '@graph', context, ordered: ordered)
|
221
|
+
value.is_a?(Array) ? value : [value]
|
222
|
+
when '@value'
|
223
|
+
# 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.
|
224
|
+
# If framing, always use array form, unless null
|
225
|
+
case value
|
226
|
+
when String, TrueClass, FalseClass, Numeric then (framing ? [value] : value)
|
227
|
+
when nil
|
228
|
+
output_object['@value'] = nil
|
229
|
+
next;
|
230
|
+
when Array
|
231
|
+
raise JsonLdError::InvalidValueObjectValue,
|
232
|
+
"@value value may not be an array unless framing: #{value.inspect}" unless framing
|
233
|
+
value
|
234
|
+
when Hash
|
235
|
+
raise JsonLdError::InvalidValueObjectValue,
|
236
|
+
"@value value must be a an empty object for framing: #{value.inspect}" unless
|
237
|
+
value.empty? && framing
|
238
|
+
[value]
|
239
|
+
else
|
240
|
+
raise JsonLdError::InvalidValueObjectValue,
|
241
|
+
"Value of #{expanded_property} must be a scalar or null: #{value.inspect}"
|
242
|
+
end
|
243
|
+
when '@language'
|
244
|
+
# 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.
|
245
|
+
# If framing, always use array form, unless null
|
246
|
+
case value
|
247
|
+
when String then (framing ? [value.downcase] : value.downcase)
|
248
|
+
when Array
|
249
|
+
raise JsonLdError::InvalidLanguageTaggedString,
|
250
|
+
"@language value may not be an array unless framing: #{value.inspect}" unless framing
|
251
|
+
value.map(&:downcase)
|
252
|
+
when Hash
|
253
|
+
raise JsonLdError::InvalidLanguageTaggedString,
|
254
|
+
"@language value must be a an empty object for framing: #{value.inspect}" unless
|
255
|
+
value.empty? && framing
|
256
|
+
[value]
|
257
|
+
else
|
258
|
+
raise JsonLdError::InvalidLanguageTaggedString,
|
259
|
+
"Value of #{expanded_property} must be a string: #{value.inspect}"
|
260
|
+
end
|
261
|
+
when '@index'
|
262
|
+
# 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.
|
263
|
+
raise JsonLdError::InvalidIndexValue,
|
264
|
+
"Value of @index is not a string: #{value.inspect}" unless value.is_a?(String)
|
265
|
+
value
|
266
|
+
when '@list'
|
267
|
+
# If expanded property is @list:
|
268
|
+
|
269
|
+
# If active property is null or @graph, continue with the next key from element to remove the free-floating list.
|
270
|
+
next if (active_property || '@graph') == '@graph'
|
271
|
+
|
272
|
+
# Otherwise, initialize expanded value to the result of using this algorithm recursively passing active context, active property, and value for element.
|
273
|
+
value = expand(value, active_property, context, ordered: ordered)
|
274
|
+
|
275
|
+
# Spec FIXME: need to be sure that result is an array
|
276
|
+
value = [value] unless value.is_a?(Array)
|
277
|
+
|
278
|
+
# If expanded value is a list object, a list of lists error has been detected and processing is aborted.
|
279
|
+
# Spec FIXME: Also look at each object if result is an array
|
280
|
+
raise JsonLdError::ListOfLists,
|
281
|
+
"A list may not contain another list" if value.any? {|v| list?(v)}
|
282
|
+
|
283
|
+
value
|
284
|
+
when '@set'
|
285
|
+
# 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.
|
286
|
+
expand(value, active_property, context, ordered: ordered)
|
287
|
+
when '@reverse'
|
288
|
+
# If expanded property is @reverse and value is not a JSON object, an invalid @reverse value error has been detected and processing is aborted.
|
289
|
+
raise JsonLdError::InvalidReverseValue,
|
290
|
+
"@reverse value must be an object: #{value.inspect}" unless value.is_a?(Hash)
|
291
|
+
|
292
|
+
# Otherwise
|
293
|
+
# Initialize expanded value to the result of using this algorithm recursively, passing active context, @reverse as active property, and value as element.
|
294
|
+
value = expand(value, '@reverse', context, ordered: ordered)
|
295
|
+
|
296
|
+
# 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:
|
297
|
+
if value.has_key?('@reverse')
|
298
|
+
#log_debug("@reverse") {"double reverse: #{value.inspect}"}
|
299
|
+
value['@reverse'].each do |property, item|
|
300
|
+
# If result does not have a property member, create one and set its value to an empty array.
|
301
|
+
# Append item to the value of the property member of result.
|
302
|
+
(output_object[property] ||= []).concat([item].flatten.compact)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# If expanded value contains members other than @reverse:
|
307
|
+
unless value.keys.reject {|k| k == '@reverse'}.empty?
|
308
|
+
# If result does not have an @reverse member, create one and set its value to an empty JSON object.
|
309
|
+
reverse_map = output_object['@reverse'] ||= {}
|
310
|
+
value.each do |property, items|
|
311
|
+
next if property == '@reverse'
|
312
|
+
items.each do |item|
|
313
|
+
if value?(item) || list?(item)
|
314
|
+
raise JsonLdError::InvalidReversePropertyValue,
|
315
|
+
item.inspect
|
316
|
+
end
|
317
|
+
merge_value(reverse_map, property, item)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
# Continue with the next key from element
|
323
|
+
next
|
324
|
+
when '@default', '@embed', '@explicit', '@omitDefault', '@preserve', '@requireAll'
|
325
|
+
next unless framing
|
326
|
+
# Framing keywords
|
327
|
+
[expand(value, expanded_property, context, ordered: ordered)].flatten
|
328
|
+
when '@nest'
|
329
|
+
# Add key to nests
|
330
|
+
nests << key
|
331
|
+
# Continue with the next key from element
|
332
|
+
next
|
333
|
+
else
|
334
|
+
# Skip unknown keyword
|
335
|
+
next
|
336
|
+
end
|
337
|
+
|
338
|
+
# Unless expanded value is null, set the expanded property member of result to expanded value.
|
339
|
+
#log_debug("expand #{expanded_property}") { expanded_value.inspect}
|
340
|
+
output_object[expanded_property] = expanded_value unless expanded_value.nil?
|
341
|
+
next
|
342
|
+
end
|
343
|
+
|
344
|
+
# Use a term-specific context, if defined
|
345
|
+
term_context = context.term_definitions[key].context if context.term_definitions[key]
|
346
|
+
active_context = term_context ? context.parse(term_context) : context
|
347
|
+
container = active_context.container(key)
|
348
|
+
expanded_value = if container == '@language' && value.is_a?(Hash)
|
349
|
+
# Otherwise, if key's container mapping in active context is @language and value is a JSON object then value is expanded from a language map as follows:
|
350
|
+
|
351
|
+
# Set multilingual array to an empty array.
|
352
|
+
ary = []
|
353
|
+
|
354
|
+
# For each key-value pair language-language value in value, ordered lexicographically by language
|
355
|
+
keys = ordered ? value.keys.sort : value.keys
|
356
|
+
keys.each do |k|
|
357
|
+
[value[k]].flatten.each do |item|
|
358
|
+
# item must be a string, otherwise an invalid language map value error has been detected and processing is aborted.
|
359
|
+
raise JsonLdError::InvalidLanguageMapValue,
|
360
|
+
"Expected #{item.inspect} to be a string" unless item.nil? || item.is_a?(String)
|
361
|
+
|
362
|
+
# Append a JSON object to expanded value that consists of two key-value pairs: (@value-item) and (@language-lowercased language).
|
363
|
+
ary << {
|
364
|
+
'@value' => item,
|
365
|
+
'@language' => k.downcase
|
366
|
+
} if item
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
ary
|
371
|
+
elsif %w(@index @id @type).include?(container) && value.is_a?(Hash)
|
372
|
+
# Otherwise, if key's container mapping in active context is @index, @id, @type, an IRI or Blank Node and value is a JSON object then value is expanded from an index map as follows:
|
373
|
+
|
374
|
+
# Set ary to an empty array.
|
375
|
+
container, ary = container.to_s, []
|
376
|
+
|
377
|
+
# For each key-value in the object:
|
378
|
+
keys = ordered ? value.keys.sort : value.keys
|
379
|
+
keys.each do |k|
|
380
|
+
# If container mapping in the active context is @type, and k is a term in the active context having a local context, use that context when expanding values
|
381
|
+
map_context = active_context.term_definitions[k].context if container == '@type' && active_context.term_definitions[k]
|
382
|
+
map_context = active_context.parse(map_context) if map_context
|
383
|
+
map_context ||= active_context
|
384
|
+
|
385
|
+
# Initialize index value to the result of using this algorithm recursively, passing active context, key as active property, and index value as element.
|
386
|
+
index_value = expand([value[k]].flatten, key, map_context, ordered: ordered)
|
387
|
+
#require 'byebug'; byebug
|
388
|
+
index_value.each do |item|
|
389
|
+
case container
|
390
|
+
when '@index' then item[container] ||= k
|
391
|
+
when '@id'
|
392
|
+
# Expand k document relative
|
393
|
+
expanded_k = active_context.expand_iri(k, documentRelative: true, quiet: true).to_s
|
394
|
+
item[container] ||= expanded_k
|
395
|
+
when '@type'
|
396
|
+
# Expand k vocabulary relative
|
397
|
+
expanded_k = active_context.expand_iri(k, vocab: true, documentRelative: true, quiet: true).to_s
|
398
|
+
item[container] = [expanded_k].concat(Array(item[container]))
|
399
|
+
end
|
400
|
+
|
401
|
+
# Append item to expanded value.
|
402
|
+
ary << item
|
403
|
+
end
|
404
|
+
end
|
405
|
+
ary
|
406
|
+
else
|
407
|
+
# Otherwise, initialize expanded value to the result of using this algorithm recursively, passing active context, key for active property, and value for element.
|
408
|
+
expand(value, key, active_context, ordered: ordered)
|
409
|
+
end
|
410
|
+
|
411
|
+
# If expanded value is null, ignore key by continuing to the next key from element.
|
412
|
+
if expanded_value.nil?
|
413
|
+
#log_debug(" => skip nil value")
|
414
|
+
next
|
415
|
+
end
|
416
|
+
#log_debug {" => #{expanded_value.inspect}"}
|
417
|
+
|
418
|
+
# 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.
|
419
|
+
if active_context.container(key) == '@list' && !list?(expanded_value)
|
420
|
+
#log_debug(" => ") { "convert #{expanded_value.inspect} to list"}
|
421
|
+
expanded_value = {'@list' => [expanded_value].flatten}
|
422
|
+
end
|
423
|
+
#log_debug {" => #{expanded_value.inspect}"}
|
424
|
+
|
425
|
+
# Otherwise, if the term definition associated to key indicates that it is a reverse property
|
426
|
+
# Spec FIXME: this is not an otherwise.
|
427
|
+
if (td = context.term_definitions[key]) && td.reverse_property
|
428
|
+
# If result has no @reverse member, create one and initialize its value to an empty JSON object.
|
429
|
+
reverse_map = output_object['@reverse'] ||= {}
|
430
|
+
[expanded_value].flatten.each do |item|
|
431
|
+
# If item is a value object or list object, an invalid reverse property value has been detected and processing is aborted.
|
432
|
+
raise JsonLdError::InvalidReversePropertyValue,
|
433
|
+
item.inspect if value?(item) || list?(item)
|
434
|
+
|
435
|
+
# If reverse map has no expanded property member, create one and initialize its value to an empty array.
|
436
|
+
# Append item to the value of the expanded property member of reverse map.
|
437
|
+
merge_value(reverse_map, expanded_property, item)
|
438
|
+
end
|
439
|
+
else
|
440
|
+
# Otherwise, if key is not a reverse property:
|
441
|
+
# If result does not have an expanded property member, create one and initialize its value to an empty array.
|
442
|
+
(output_object[expanded_property] ||= []).concat([expanded_value].flatten)
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
# For each key in nests, recusively expand content
|
447
|
+
nests.each do |key|
|
448
|
+
nested_values = input[key]
|
449
|
+
nested_values = [input[key]] unless input[key].is_a?(Array)
|
450
|
+
nested_values.each do |nv|
|
451
|
+
raise JsonLdError::InvalidNestValue, nv.inspect unless
|
452
|
+
nv.is_a?(Hash) && nv.keys.none? {|k| context.expand_iri(k, vocab: true) == '@value'}
|
453
|
+
expand_object(nv, active_property, context, output_object, ordered: ordered)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|
397
457
|
end
|
398
458
|
end
|