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.
@@ -36,9 +36,12 @@ module JSON::LD
36
36
  # @return [String] Type mapping
37
37
  attr_accessor :type_mapping
38
38
 
39
- # @return ['@set', '@list'] Container mapping
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] simple
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', '@list'] container_mapping
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
- simple: false)
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 @options[:processingMode] == "json-ld-1.0"
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 Exception => e
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, nor @index, nor null, an invalid reverse property error has been detected (reverse properties only support set- and index-containers) and processing is aborted.
552
- if (container = value.fetch('@container', false))
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', nil].include?(container)
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
- container = value['@container']
593
- raise JsonLdError::InvalidContainerMapping, "unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" unless %w(@list @set @language @index).include?(container)
594
- #log_debug("") {"container_mapping: #{container.inspect}"}
595
- definition.container_mapping = container
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
- containers << '@index' if index?(value)
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
@@ -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
- # If expanded property is null or it neither contains a colon (:) nor it is a keyword, drop key by continuing to the next key.
53
- next if expanded_property.is_a?(RDF::URI) && expanded_property.relative?
54
- expanded_property = expanded_property.to_s if expanded_property.is_a?(RDF::Resource)
55
-
56
- #log_debug("expand property") {"ap: #{active_property.inspect}, expanded: #{expanded_property.inspect}, value: #{value.inspect}"}
57
-
58
- if expanded_property.nil?
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