json-ld 2.1.7 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e6faa8c0a2b65b20cfeaa22ac3cba60cf98ae835
4
- data.tar.gz: 9e116d70b8081c39e5b725e9053a7b40b1ecfd57
3
+ metadata.gz: 93516bb49f7999bcda524b639c817eb197acc412
4
+ data.tar.gz: 3f3a0ce771608fb40714dd05a45c9fcdec5cf21a
5
5
  SHA512:
6
- metadata.gz: 4990f799a5dd3898ce3ea7b746a96f2bf84baab2fd7b90c7f08b360cd72bb8a7863bd31e56048ddfd68ca53b4a4c7510e090c9ce49aa637d4f11298020488881
7
- data.tar.gz: 6c551095ca9137161f52c8f978c650a144cae1c070df62501a5dbb0323b3ffda0f13a00221cd257babbc09bd00a0c21d99056eece1895d955c64326b6ea935a5
6
+ metadata.gz: 80be800ae35b9906c8f289eff34b7eaa84a4d637e883f1c8d1452a4516f98daa7a63b77bddacc8791e9caf4131018ea0994f4a023557e9e9cc0587234ad85d3e
7
+ data.tar.gz: 2234dbcec6f4701da4adb9fd3b90bbf6cb3c2918c9be1394756a2ddfd10c28931d44a911e6f1a5a2fbbb7082bf9631e0da100119cccd7a1af8c3463bf9c5be60
data/README.md CHANGED
@@ -298,6 +298,86 @@ The value of `@container` in a term definition can include `@id` or `@type`, in
298
298
  }
299
299
  }
300
300
 
301
+ ### @graph containers and maps
302
+ A term can have `@container` set to include `@graph` optionally including `@id` or `@index` and `@set`. In the first form, with `@container` set to `@graph`, the value of a property is treated as a _simple graph object_, meaning that values treated as if they were contained in an object with `@graph`, creating _named graph_ with an anonymous name.
303
+
304
+ {
305
+ "@context": {
306
+ "@vocab": "http://example.org/",
307
+ "input": {"@container": "@graph"}
308
+ },
309
+ "input": {
310
+ "value": "x"
311
+ }
312
+ }
313
+
314
+ which expands to the following:
315
+
316
+ [{
317
+ "http://example.org/input": [{
318
+ "@graph": [{
319
+ "http://example.org/value": [{"@value": "x"}]
320
+ }]
321
+ }]
322
+ }]
323
+
324
+ Compaction reverses this process, optionally ensuring that a single value is contained within an array of `@container` also includes `@set`:
325
+
326
+ {
327
+ "@context": {
328
+ "@vocab": "http://example.org/",
329
+ "input": {"@container": ["@graph", "@set"]}
330
+ }
331
+ }
332
+
333
+ A graph map uses the map form already existing for `@index`, `@language`, `@type`, and `@id` where the index is either an index value or an id.
334
+
335
+ {
336
+ "@context": {
337
+ "@vocab": "http://example.org/",
338
+ "input": {"@container": ["@graph", "@index"]}
339
+ },
340
+ "input": {
341
+ "g1": {"value": "x"}
342
+ }
343
+ }
344
+
345
+ treats "g1" as an index, and expands to the following:
346
+
347
+ [{
348
+ "http://example.org/input": [{
349
+ "@index": "g1",
350
+ "@graph": [{
351
+ "http://example.org/value": [{"@value": "x"}]
352
+ }]
353
+ }]
354
+ }])
355
+
356
+ This can also include `@set` to ensure that, when compacting, a single value of an index will be in array form.
357
+
358
+ The _id_ version is similar:
359
+
360
+ {
361
+ "@context": {
362
+ "@vocab": "http://example.org/",
363
+ "input": {"@container": ["@graph", "@id"]}
364
+ },
365
+ "input": {
366
+ "http://example.com/g1": {"value": "x"}
367
+ }
368
+ }
369
+
370
+ which expands to:
371
+
372
+ [{
373
+ "http://example.org/input": [{
374
+ "@id": "http://example.com/g1",
375
+ "@graph": [{
376
+ "http://example.org/value": [{"@value": "x"}]
377
+ }]
378
+ }]
379
+ }])
380
+
301
381
  ### Transparent Nesting
302
382
  Many JSON APIs separate properties from their entities using an intermediate object. For example, a set of possible labels may be grouped under a common property:
303
383
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.1.7
1
+ 2.2.0
@@ -82,6 +82,7 @@ module JSON::LD
82
82
  if expanded_property == '@reverse'
83
83
  compacted_value = compact(expanded_value, property: '@reverse')
84
84
  #log_debug("@reverse") {"compacted_value: #{compacted_value.inspect}"}
85
+ # handle double-reversed properties
85
86
  compacted_value.each do |prop, value|
86
87
  if context.reverse?(prop)
87
88
  value = [value] if !value.is_a?(Array) &&
@@ -112,7 +113,7 @@ module JSON::LD
112
113
  next
113
114
  end
114
115
 
115
- if expanded_property == '@index' && context.container(property) == '@index'
116
+ if expanded_property == '@index' && context.container(property) == %w(@index)
116
117
  #log_debug("@index") {"drop @index"}
117
118
  next
118
119
  end
@@ -162,13 +163,20 @@ module JSON::LD
162
163
 
163
164
  container = context.container(item_active_property)
164
165
  as_array = context.as_array?(item_active_property)
165
- value = list?(expanded_item) ? expanded_item['@list'] : expanded_item
166
+
167
+ value = case
168
+ when list?(expanded_item) then expanded_item['@list']
169
+ when graph?(expanded_item) then expanded_item['@graph']
170
+ else expanded_item
171
+ end
172
+
166
173
  compacted_item = compact(value, property: item_active_property)
167
174
  #log_debug("") {" => compacted key: #{item_active_property.inspect} for #{compacted_item.inspect}"}
168
175
 
176
+ # handle @list
169
177
  if list?(expanded_item)
170
178
  compacted_item = [compacted_item] unless compacted_item.is_a?(Array)
171
- unless container == '@list'
179
+ unless container == %w(@list)
172
180
  al = context.compact_iri('@list', vocab: true, quiet: true)
173
181
  compacted_item = {al => compacted_item}
174
182
  if expanded_item.has_key?('@index')
@@ -178,25 +186,63 @@ module JSON::LD
178
186
  else
179
187
  raise JsonLdError::CompactionToListOfLists,
180
188
  "key cannot have more than one list value" if nest_result.has_key?(item_active_property)
189
+ # Falls through to add list value below
181
190
  end
182
191
  end
183
192
 
184
- if container == '@language' || container == '@index' || container == '@id' || container == '@type'
193
+ # Graph object compaction cases:
194
+ if graph?(expanded_item)
195
+ if container.include?('@graph') && container.include?('@id')
196
+ # container includes @graph and @id
197
+ map_object = nest_result[item_active_property] ||= {}
198
+ map_key = expanded_item['@id']
199
+ # If there is no @id, create a blank node identifier to use as an index
200
+ map_key = map_key ? context.compact_iri(map_key, quiet: true) : namer.get_name
201
+ merge_compacted_value(map_object, map_key, compacted_item)
202
+ elsif container.include?('@graph') && container.include?('@index') && simple_graph?(expanded_item)
203
+ # container includes @graph and @index and value is a simple graph object
204
+ map_object = nest_result[item_active_property] ||= {}
205
+ # If there is no @index, use @none
206
+ map_key = expanded_item['@index'] || '@none'
207
+ merge_compacted_value(map_object, map_key, compacted_item)
208
+ elsif container.include?('@graph') && simple_graph?(expanded_item)
209
+ # container includes @graph but not @id or @index and value is a simple graph object
210
+ # Drop through, where compacted_value will be added
211
+ compacted_item = [compacted_item] if
212
+ !compacted_item.is_a?(Array) && (!@options[:compactArrays] || as_array)
213
+ merge_compacted_value(nest_result, item_active_property, compacted_item)
214
+ else
215
+ # container does not include @graph or otherwise does not match one of the previous cases, redo compacted_item
216
+ compacted_item = [compacted_item]
217
+ al = context.compact_iri('@graph', vocab: true, quiet: true)
218
+ compacted_item = {al => compacted_item}
219
+ if expanded_item['@id']
220
+ al = context.compact_iri('@id', vocab: true, quiet: true)
221
+ compacted_item[al] = context.compact_iri(expanded_item['@id'], vocab: false, quiet: true).to_s
222
+ end
223
+ if expanded_item.has_key?('@index')
224
+ key = context.compact_iri('@index', vocab: true, quiet: true)
225
+ compacted_item[key] = expanded_item['@index']
226
+ end
227
+ compacted_item = [compacted_item] if !@options[:compactArrays] || as_array
228
+ merge_compacted_value(nest_result, item_active_property, compacted_item)
229
+ end
230
+ elsif !(container & %w(@language @index @id @type)).empty? && !container.include?('@graph')
185
231
  map_object = nest_result[item_active_property] ||= {}
186
232
  compacted_item = case container
187
- when '@id'
233
+ when %w(@id)
188
234
  id_prop = context.compact_iri('@id', vocab: true, quiet: true)
189
235
  map_key = compacted_item[id_prop]
190
236
  map_key = context.compact_iri(map_key, quiet: true)
191
237
  compacted_item.delete(id_prop)
192
238
  compacted_item
193
- when '@index'
194
- map_key = expanded_item[container]
239
+ when %w(@index)
240
+ map_key = expanded_item['@index']
195
241
  compacted_item
196
- when '@language'
197
- map_key = expanded_item[container]
242
+ when %w(@language)
243
+ map_key = expanded_item['@language']
198
244
  value?(expanded_item) ? expanded_item['@value'] : compacted_item
199
- when '@type'
245
+ when %w(@type)
200
246
  type_prop = context.compact_iri('@type', vocab: true, quiet: true)
201
247
  map_key, *types = Array(compacted_item[type_prop])
202
248
  map_key = context.compact_iri(map_key, vocab: true, quiet: true)
@@ -38,13 +38,9 @@ module JSON::LD
38
38
  attr_accessor :type_mapping
39
39
 
40
40
  # Base container mapping, without @set
41
- # @return ['@index', '@language', '@index', '@type', '@id'] Container mapping
41
+ # @return Array<'@index', '@language', '@index', '@set', '@type', '@id', '@graph'> Container mapping
42
42
  attr_reader :container_mapping
43
43
 
44
- # If container mapping was defined along with @set
45
- # @return [Boolean]
46
- attr_reader :as_set
47
-
48
44
  # @return [String] Term used for nest properties
49
45
  attr_accessor :nest
50
46
 
@@ -78,7 +74,7 @@ module JSON::LD
78
74
  # @param [String] term
79
75
  # @param [String] id
80
76
  # @param [String] type_mapping Type mapping
81
- # @param ['@index', '@language', '@index', '@set', '@type', '@id'] container_mapping
77
+ # @param [Array<'@index', '@language', '@index', '@set', '@type', '@id', '@graph'>] container_mapping
82
78
  # @param [String] language_mapping
83
79
  # Language mapping of term, `false` is used if there is explicitly no language mapping for this term
84
80
  # @param [Boolean] reverse_property
@@ -100,7 +96,7 @@ module JSON::LD
100
96
  @term = term
101
97
  @id = id.to_s unless id.nil?
102
98
  @type_mapping = type_mapping.to_s unless type_mapping.nil?
103
- self.container_mapping = container_mapping unless container_mapping.nil?
99
+ self.container_mapping = container_mapping
104
100
  @language_mapping = language_mapping unless language_mapping.nil?
105
101
  @reverse_property = reverse_property
106
102
  @nest = nest unless nest.nil?
@@ -116,7 +112,7 @@ module JSON::LD
116
112
  mapping = mapping.dup
117
113
  mapping.delete('@set')
118
114
  end
119
- @container_mapping = mapping.first
115
+ @container_mapping = mapping.sort
120
116
  end
121
117
 
122
118
  ##
@@ -147,7 +143,7 @@ module JSON::LD
147
143
  end
148
144
  end
149
145
 
150
- cm = [container_mapping, ('@set' if as_set)].compact
146
+ cm = (Array(container_mapping) + (as_set? ? %w(@set) : [])).compact
151
147
  cm = cm.first if cm.length == 1
152
148
  defn['@container'] = cm unless cm.empty?
153
149
  # Language set as false to be output as null
@@ -168,21 +164,26 @@ module JSON::LD
168
164
  %w(id type_mapping container_mapping language_mapping reverse_property nest simple prefix context).each do |acc|
169
165
  v = instance_variable_get("@#{acc}".to_sym)
170
166
  v = v.to_s if v.is_a?(RDF::Term)
171
- if acc == 'container_mapping' && as_set
172
- v = v ? [v, '@set'] : '@set'
167
+ if acc == 'container_mapping'
168
+ v.concat(%w(@set)) if as_set?
169
+ v = v.first if v.length <= 1
173
170
  end
174
171
  defn << "#{acc}: #{v.inspect}" if v
175
172
  end
176
173
  defn.join(', ') + ")"
177
174
  end
178
175
 
176
+ # If container mapping was defined along with @set
177
+ # @return [Boolean]
178
+ def as_set?; @as_set || false; end
179
+
179
180
  def inspect
180
181
  v = %w([TD)
181
182
  v << "id=#{@id}"
182
183
  v << "term=#{@term}"
183
184
  v << "rev" if reverse_property
184
185
  v << "container=#{container_mapping}" if container_mapping
185
- v << "as_set=#{as_set.inspect}"
186
+ v << "as_set=#{as_set?.inspect}"
186
187
  v << "lang=#{language_mapping.inspect}" unless language_mapping.nil?
187
188
  v << "type=#{type_mapping}" unless type_mapping.nil?
188
189
  v << "nest=#{nest.inspect}" unless nest.nil?
@@ -874,23 +875,22 @@ module JSON::LD
874
875
  # Retrieve container mapping, add it if `value` is provided
875
876
  #
876
877
  # @param [Term, #to_s] term in unexpanded form
877
- # @return [String]
878
+ # @return [Array<'@index', '@language', '@index', '@set', '@type', '@id', '@graph'>]
878
879
  def container(term)
879
- return '@set' if term == '@graph'
880
- return term if KEYWORDS.include?(term)
880
+ return [term] if KEYWORDS.include?(term)
881
881
  term = find_definition(term)
882
- term && term.container_mapping
882
+ term ? term.container_mapping : []
883
883
  end
884
884
 
885
885
  ##
886
- # Should values be represented as a set?
886
+ # Should values be represented using an array?
887
887
  #
888
888
  # @param [Term, #to_s] term in unexpanded form
889
889
  # @return [Boolean]
890
890
  def as_array?(term)
891
- return true if term == '@graph' || term == '@list'
891
+ return true if CONTEXT_CONTAINER_ARRAY_TERMS.include?(term)
892
892
  term = find_definition(term)
893
- term && (term.as_set || term.container_mapping == '@list')
893
+ term && (term.as_set? || term.container_mapping.include?('@list'))
894
894
  end
895
895
 
896
896
  ##
@@ -1122,6 +1122,11 @@ module JSON::LD
1122
1122
  tl_value = common_language
1123
1123
  end
1124
1124
  #log_debug("") {"list: containers: #{containers.inspect}, type/language: #{tl.inspect}, type/language value: #{tl_value.inspect}"} unless quiet
1125
+ elsif graph?(value)
1126
+ # TODO: support `@graphId`?
1127
+ # TODO: "@graph@set"?
1128
+ containers << '@graph'
1129
+ containers << '@set'
1125
1130
  else
1126
1131
  if value?(value)
1127
1132
  if value.has_key?('@language') && !index?(value)
@@ -1228,14 +1233,16 @@ module JSON::LD
1228
1233
  def expand_value(property, value, useNativeTypes: false, **options)
1229
1234
  #log_debug("expand_value") {"property: #{property.inspect}, value: #{value.inspect}"}
1230
1235
 
1236
+ td = term_definitions.fetch(property, TermDefinition.new(property))
1237
+
1231
1238
  # If the active property has a type mapping in active context that is @id, return a new JSON object containing a single key-value pair where the key is @id and the value is the result of using the IRI Expansion algorithm, passing active context, value, and true for document relative.
1232
- if (td = term_definitions.fetch(property, TermDefinition.new(property))) && td.type_mapping == '@id'
1239
+ if value.is_a?(String) && td.type_mapping == '@id'
1233
1240
  #log_debug("") {"as relative IRI: #{value.inspect}"}
1234
1241
  return {'@id' => expand_iri(value, documentRelative: true).to_s}
1235
1242
  end
1236
1243
 
1237
1244
  # If active property has a type mapping in active context that is @vocab, return a new JSON object containing a single key-value pair where the key is @id and the value is the result of using the IRI Expansion algorithm, passing active context, value, true for vocab, and true for document relative.
1238
- if td.type_mapping == '@vocab'
1245
+ if value.is_a?(String) && td.type_mapping == '@vocab'
1239
1246
  #log_debug("") {"as vocab IRI: #{value.inspect}"}
1240
1247
  return {'@id' => expand_iri(value, vocab: true, documentRelative: true).to_s}
1241
1248
  end
@@ -1271,15 +1278,14 @@ module JSON::LD
1271
1278
  # Otherwise, initialize result to a JSON object with an @value member whose value is set to value.
1272
1279
  res = {'@value' => value}
1273
1280
 
1274
- if td.type_mapping
1281
+ if td.type_mapping && !%w(@id @vocab).include?(td.type_mapping.to_s)
1275
1282
  res['@type'] = td.type_mapping.to_s
1276
- elsif value.is_a?(String)
1277
- if td.language_mapping
1278
- res['@language'] = td.language_mapping
1279
- elsif default_language && td.language_mapping.nil?
1280
- res['@language'] = default_language
1281
- end
1283
+ elsif value.is_a?(String) && td.language_mapping
1284
+ res['@language'] = td.language_mapping
1285
+ elsif value.is_a?(String) && default_language && td.language_mapping.nil?
1286
+ res['@language'] = default_language
1282
1287
  end
1288
+
1283
1289
  res
1284
1290
  end
1285
1291
 
@@ -1305,7 +1311,7 @@ module JSON::LD
1305
1311
 
1306
1312
  num_members = value.length
1307
1313
 
1308
- num_members -= 1 if index?(value) && container(property) == '@index'
1314
+ num_members -= 1 if index?(value) && container(property).include?('@index')
1309
1315
  if num_members > 2
1310
1316
  #log_debug("") {"can't compact value with # members > 2"}
1311
1317
  return value
@@ -1442,6 +1448,10 @@ module JSON::LD
1442
1448
 
1443
1449
  private
1444
1450
 
1451
+ CONTEXT_CONTAINER_ARRAY_TERMS = %w(@set @list @graph).freeze
1452
+ CONTEXT_CONTAINER_ID_GRAPH = %w(@id @graph).freeze
1453
+ CONTEXT_CONTAINER_INDEX_GRAPH = %w(@index @graph).freeze
1454
+
1445
1455
  def uri(value)
1446
1456
  case value.to_s
1447
1457
  when /^_:(.*)$/
@@ -1482,7 +1492,26 @@ module JSON::LD
1482
1492
  #
1483
1493
  # To make use of an inverse context, a list of preferred container mappings and the type mapping or language mapping are gathered for a particular value associated with an IRI. These parameters are then fed to the Term Selection algorithm, which will find the term that most appropriately matches the value's mappings.
1484
1494
  #
1495
+ # @example Basic structure of resulting inverse context
1496
+ # {
1497
+ # "http://example.com/term": {
1498
+ # "@language": {
1499
+ # "@null": "term",
1500
+ # "@none": "term",
1501
+ # "en": "term"
1502
+ # },
1503
+ # "@type": {
1504
+ # "@reverse": "term",
1505
+ # "@none": "term",
1506
+ # "http://datatype": "term"
1507
+ # },
1508
+ # "@any": {
1509
+ # "@none": "term",
1510
+ # }
1511
+ # }
1512
+ # }
1485
1513
  # @return [Hash{String => Hash{String => String}}]
1514
+ # @todo May want to include @set along with container to allow selecting terms using @set over those without @set. May require adding some notion of value cardinality to compact_iri
1486
1515
  def inverse_context
1487
1516
  @inverse_context ||= begin
1488
1517
  result = {}
@@ -1491,7 +1520,15 @@ module JSON::LD
1491
1520
  a.length == b.length ? (a <=> b) : (a.length <=> b.length)
1492
1521
  end.each do |term|
1493
1522
  next unless td = term_definitions[term]
1494
- container = td.container_mapping || (td.as_set ? '@set' : '@none')
1523
+
1524
+ container = Array(td.container_mapping).sort.first
1525
+ container ||= td.as_set? ? %(@set) : %(@none)
1526
+ # FIXME: Alternative to consider
1527
+ ## Creates "@language", "@language@set", "@set", or "@none"
1528
+ ## for each of "@language", "@index", "@type", "@id", "@list", and "@graph"
1529
+ #container = td.container_mapping.to_s
1530
+ #container += '@set' if td.as_set?
1531
+ #container = '@none' if container.empty?
1495
1532
  container_map = result[td.id.to_s] ||= {}
1496
1533
  tl_map = container_map[container] ||= {'@language' => {}, '@type' => {}, '@any' => {}}
1497
1534
  type_map = tl_map['@type']
@@ -1620,20 +1657,45 @@ module JSON::LD
1620
1657
  val = Array(container).dup
1621
1658
  val.delete('@set') if has_set = val.include?('@set')
1622
1659
 
1623
- raise JsonLdError::InvalidContainerMapping,
1624
- "'@container' has more than one value other than @set" if val.length > 1
1625
-
1626
- case val.first
1627
- when '@list'
1660
+ if val.include?('@list')
1628
1661
  raise JsonLdError::InvalidContainerMapping,
1629
- "'@container' on term #{term.inspect} cannot be both @list and @set" if has_set
1662
+ "'@container' on term #{term.inspect} using @list cannot have any other values" unless
1663
+ !has_set && val.length == 1
1630
1664
  # Okay
1631
- when '@language', '@index', nil
1665
+ elsif val.include?('@language')
1666
+ raise JsonLdError::InvalidContainerMapping,
1667
+ "unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" if
1668
+ has_set && (processingMode || 'json-ld-1.0') < 'json-ld-1.1'
1669
+ raise JsonLdError::InvalidContainerMapping,
1670
+ "'@container' on term #{term.inspect} using @language cannot have any values other than @set, found #{container.inspect}" unless
1671
+ val.length == 1
1672
+ # Okay
1673
+ elsif val.include?('@index')
1674
+ raise JsonLdError::InvalidContainerMapping,
1675
+ "unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" if
1676
+ has_set && (processingMode || 'json-ld-1.0') < 'json-ld-1.1'
1677
+ raise JsonLdError::InvalidContainerMapping,
1678
+ "'@container' on term #{term.inspect} using @index cannot have any values other than @set and/or @graph, found #{container.inspect}" unless
1679
+ (val - CONTEXT_CONTAINER_INDEX_GRAPH).empty?
1680
+ # Okay
1681
+ elsif val.include?('@id')
1682
+ raise JsonLdError::InvalidContainerMapping,
1683
+ "unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" if
1684
+ (processingMode || 'json-ld-1.0') < 'json-ld-1.1'
1685
+ raise JsonLdError::InvalidContainerMapping,
1686
+ "'@container' on term #{term.inspect} using @id cannot have any values other than @set and/or @graph, found #{container.inspect}" unless
1687
+ (val - CONTEXT_CONTAINER_ID_GRAPH).empty?
1632
1688
  # Okay
1633
- when '@type', '@id', nil
1689
+ elsif val.include?('@type') || val.include?('@graph')
1634
1690
  raise JsonLdError::InvalidContainerMapping,
1635
1691
  "unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" if
1636
1692
  (processingMode || 'json-ld-1.0') < 'json-ld-1.1'
1693
+ raise JsonLdError::InvalidContainerMapping,
1694
+ "'@container' on term #{term.inspect} using @language cannot have any values other than @set, found #{container.inspect}" unless
1695
+ val.length == 1
1696
+ # Okay
1697
+ elsif val.empty?
1698
+ # Okay
1637
1699
  else
1638
1700
  raise JsonLdError::InvalidContainerMapping,
1639
1701
  "unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}"