json-ld 3.1.7 → 3.1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,9 +11,9 @@ module JSON::LD
11
11
  # The following constant is used to reduce object allocations
12
12
  CONTAINER_INDEX_ID_TYPE = Set['@index', '@id', '@type'].freeze
13
13
  KEY_ID = %w(@id).freeze
14
- KEYS_VALUE_LANGUAGE_TYPE_INDEX_DIRECTION = %w(@value @language @type @index @direction).freeze
14
+ KEYS_VALUE_LANGUAGE_TYPE_INDEX_DIRECTION = %w(@value @language @type @index @direction @annotation).freeze
15
15
  KEYS_SET_LIST_INDEX = %w(@set @list @index).freeze
16
- KEYS_INCLUDED_TYPE = %w(@included @type).freeze
16
+ KEYS_INCLUDED_TYPE_REVERSE = %w(@included @type @reverse).freeze
17
17
 
18
18
  ##
19
19
  # Expand an Array or Object given an active context and performing local context expansion.
@@ -49,7 +49,13 @@ module JSON::LD
49
49
  log_depth: log_depth.to_i + 1)
50
50
 
51
51
  # If the active property is @list or its container mapping is set to @list and v is an array, change it to a list object
52
- v = {"@list" => v} if is_list && v.is_a?(Array)
52
+ if is_list && v.is_a?(Array)
53
+ # Make sure that no member of v contains an annotation object
54
+ raise JsonLdError::InvalidAnnotation,
55
+ "A list element must not contain @annotation." if
56
+ v.any? {|n| n.is_a?(Hash) && n.key?('@annotation')}
57
+ v = {"@list" => v}
58
+ end
53
59
 
54
60
  case v
55
61
  when nil then nil
@@ -81,7 +87,7 @@ module JSON::LD
81
87
  log_debug("expand", depth: log_depth.to_i) {"after property_scoped_context: #{context.inspect}"} unless property_scoped_context.nil?
82
88
 
83
89
  # If element contains the key @context, set active context to the result of the Context Processing algorithm, passing active context and the value of the @context key as local context.
84
- if input.has_key?('@context')
90
+ if input.key?('@context')
85
91
  context = context.parse(input.delete('@context'), base: @options[:base])
86
92
  log_debug("expand", depth: log_depth.to_i) {"context: #{context.inspect}"}
87
93
  end
@@ -142,7 +148,7 @@ module JSON::LD
142
148
 
143
149
  if output_object['@type'] == '@json' && context.processingMode('json-ld-1.1')
144
150
  # Any value of @value is okay if @type: @json
145
- elsif !ary.all? {|v| v.is_a?(String) || v.is_a?(Hash) && v.empty?} && output_object.has_key?('@language')
151
+ elsif !ary.all? {|v| v.is_a?(String) || v.is_a?(Hash) && v.empty?} && output_object.key?('@language')
146
152
  # Otherwise, if the value of result's @value member is not a string and result contains the key @language, an invalid language-tagged value error has been detected (only strings can be language-tagged) and processing is aborted.
147
153
  raise JsonLdError::InvalidLanguageTaggedValue,
148
154
  "when @language is used, @value must be a string: #{output_object.inspect}"
@@ -172,6 +178,18 @@ module JSON::LD
172
178
 
173
179
  # If result contains the key @set, then set result to the key's associated value.
174
180
  return output_object['@set'] if output_object.key?('@set')
181
+ elsif output_object['@annotation']
182
+ # Otherwise, if result contains the key @annotation,
183
+ # the array value must all be node objects without an @id property, otherwise, an invalid annotation error has been detected and processing is aborted.
184
+ raise JsonLdError::InvalidAnnotation,
185
+ "@annotation must reference node objects without @id." unless
186
+ output_object['@annotation'].all? {|o| node?(o) && !o.key?('@id')}
187
+
188
+ # Additionally, the property must not be used if there is no active property, or the expanded active property is @graph.
189
+ raise JsonLdError::InvalidAnnotation,
190
+ "@annotation must not be used on a top-level object." if
191
+ %w(@graph @included).include?(expanded_active_property || '@graph')
192
+
175
193
  end
176
194
 
177
195
  # If result contains only the key @language, set result to null.
@@ -254,10 +272,15 @@ module JSON::LD
254
272
 
255
273
  # If result has already an expanded property member (other than @type), an colliding keywords error has been detected and processing is aborted.
256
274
  raise JsonLdError::CollidingKeywords,
257
- "#{expanded_property} already exists in result" if output_object.has_key?(expanded_property) && !KEYS_INCLUDED_TYPE.include?(expanded_property)
275
+ "#{expanded_property} already exists in result" if output_object.key?(expanded_property) && !KEYS_INCLUDED_TYPE_REVERSE.include?(expanded_property)
258
276
 
259
277
  expanded_value = case expanded_property
260
278
  when '@id'
279
+ # If expanded active property is `@annotation`, an invalid annotation error has been found and processing is aborted.
280
+ raise JsonLdError::InvalidAnnotation,
281
+ "an annotation must not contain a property expanding to @id" if
282
+ expanded_active_property == '@annotation' && @options[:rdfstar]
283
+
261
284
  # If expanded property is @id and value is not a string, an invalid @id value error has been detected and processing is aborted
262
285
  e_id = case value
263
286
  when String
@@ -280,7 +303,11 @@ module JSON::LD
280
303
  [{}]
281
304
  elsif @options[:rdfstar]
282
305
  # Result must have just a single statement
283
- rei_node = expand(value, active_property, context, log_depth: log_depth.to_i + 1)
306
+ rei_node = expand(value, nil, context, log_depth: log_depth.to_i + 1)
307
+
308
+ # Node must not contain @reverse
309
+ raise JsonLdError::InvalidEmbeddedNode,
310
+ "Embedded node with @reverse" if rei_node && rei_node.key?('@reverse')
284
311
  statements = to_enum(:item_to_rdf, rei_node)
285
312
  raise JsonLdError::InvalidEmbeddedNode,
286
313
  "Embedded node with #{statements.size} statements" unless
@@ -465,6 +492,11 @@ module JSON::LD
465
492
  # Spec FIXME: need to be sure that result is an array
466
493
  value = as_array(value)
467
494
 
495
+ # Make sure that no member of value contains an annotation object
496
+ raise JsonLdError::InvalidAnnotation,
497
+ "A list element must not contain @annotation." if
498
+ value.any? {|n| n.is_a?(Hash) && n.key?('@annotation')}
499
+
468
500
  value
469
501
  when '@set'
470
502
  # If expanded property is @set, set expanded value to the result of using this algorithm recursively, passing active context, active property, and value for element.
@@ -483,7 +515,7 @@ module JSON::LD
483
515
  log_depth: log_depth.to_i + 1)
484
516
 
485
517
  # If expanded value contains an @reverse member, i.e., properties that are reversed twice, execute for each of its property and item the following steps:
486
- if value.has_key?('@reverse')
518
+ if value.key?('@reverse')
487
519
  #log_debug("@reverse", depth: log_depth.to_i) {"double reverse: #{value.inspect}"}
488
520
  value['@reverse'].each do |property, item|
489
521
  # If result does not have a property member, create one and set its value to an empty array.
@@ -522,6 +554,12 @@ module JSON::LD
522
554
  nests << key
523
555
  # Continue with the next key from element
524
556
  next
557
+ when '@annotation'
558
+ # Skip unless rdfstar option is set
559
+ next unless @options[:rdfstar]
560
+ as_array(expand(value, '@annotation', context,
561
+ framing: framing,
562
+ log_depth: log_depth.to_i + 1))
525
563
  else
526
564
  # Skip unknown keyword
527
565
  next
@@ -11,10 +11,10 @@ module RDF
11
11
  class Statement
12
12
  # Validate extended RDF
13
13
  def valid_extended?
14
- has_subject? && subject.resource? && subject.valid_extended? &&
15
- has_predicate? && predicate.resource? && predicate.valid_extended? &&
16
- has_object? && object.term? && object.valid_extended? &&
17
- (has_graph? ? (graph_name.resource? && graph_name.valid_extended?) : true)
14
+ subject? && subject.resource? && subject.valid_extended? &&
15
+ predicate? && predicate.resource? && predicate.valid_extended? &&
16
+ object? && object.term? && object.valid_extended? &&
17
+ (graph? ? (graph_name.resource? && graph_name.valid_extended?) : true)
18
18
  end
19
19
  end
20
20
 
@@ -1,5 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  # frozen_string_literal: true
3
+ require 'json/canonicalization'
4
+
3
5
  module JSON::LD
4
6
  module Flatten
5
7
  include Utils
@@ -7,6 +9,10 @@ module JSON::LD
7
9
  ##
8
10
  # This algorithm creates a JSON object node map holding an indexed representation of the graphs and nodes represented in the passed expanded document. All nodes that are not uniquely identified by an IRI get assigned a (new) blank node identifier. The resulting node map will have a member for every graph in the document whose value is another object with a member for every node represented in the document. The default graph is stored under the @default member, all other graphs are stored under their graph name.
9
11
  #
12
+ # For RDF*/JSON-LD*:
13
+ # * Values of `@id` can be an object (embedded node); when these are used as keys in a Node Map, they are serialized as canonical JSON, and de-serialized when flattening.
14
+ # * The presence of `@annotation` implies an embedded node and the annotation object is removed from the node/value object in which it appears.
15
+ #
10
16
  # @param [Array, Hash] element
11
17
  # Expanded JSON-LD input
12
18
  # @param [Hash] graph_map A map of graph name to subjects
@@ -16,12 +22,15 @@ module JSON::LD
16
22
  # Node identifier
17
23
  # @param [String] active_property (nil)
18
24
  # Property within current node
25
+ # @param [Boolean] reverse (false)
26
+ # Processing a reverse relationship
19
27
  # @param [Array] list (nil)
20
28
  # Used when property value is a list
21
29
  def create_node_map(element, graph_map,
22
30
  active_graph: '@default',
23
31
  active_subject: nil,
24
32
  active_property: nil,
33
+ reverse: false,
25
34
  list: nil)
26
35
  if element.is_a?(Array)
27
36
  # If element is an array, process each entry in element recursively by passing item for element, node map, active graph, active subject, active property, and list.
@@ -30,21 +39,45 @@ module JSON::LD
30
39
  active_graph: active_graph,
31
40
  active_subject: active_subject,
32
41
  active_property: active_property,
42
+ reverse: false,
33
43
  list: list)
34
44
  end
35
45
  elsif !element.is_a?(Hash)
36
46
  raise "Expected hash or array to create_node_map, got #{element.inspect}"
37
47
  else
38
48
  graph = (graph_map[active_graph] ||= {})
39
- subject_node = graph[active_subject]
49
+ subject_node = !reverse && graph[active_subject.is_a?(Hash) ? active_subject.to_json_c14n : active_subject]
40
50
 
41
51
  # Transform BNode types
42
- if element.has_key?('@type')
52
+ if element.key?('@type')
43
53
  element['@type'] = Array(element['@type']).map {|t| blank_node?(t) ? namer.get_name(t) : t}
44
54
  end
45
55
 
46
56
  if value?(element)
47
57
  element['@type'] = element['@type'].first if element ['@type']
58
+
59
+ # For rdfstar, if value contains an `@annotation` member ...
60
+ # note: active_subject will not be nil, and may be an object itself.
61
+ if element.key?('@annotation')
62
+ # rdfstar being true is implicit, as it is checked in expansion
63
+ as = node_reference?(active_subject) ?
64
+ active_subject['@id'] :
65
+ active_subject
66
+ star_subject = {
67
+ "@id" => as,
68
+ active_property => [element]
69
+ }
70
+
71
+ # Note that annotation is an array, make the reified subject the id of each member of that array.
72
+ annotation = element.delete('@annotation').map do |a|
73
+ a.merge('@id' => star_subject)
74
+ end
75
+
76
+ # Invoke recursively using annotation.
77
+ create_node_map(annotation, graph_map,
78
+ active_graph: active_graph)
79
+ end
80
+
48
81
  if list.nil?
49
82
  add_value(subject_node, active_property, element, property_is_array: true, allow_duplicate: false)
50
83
  else
@@ -64,13 +97,19 @@ module JSON::LD
64
97
  end
65
98
  else
66
99
  # Element is a node object
67
- id = element.delete('@id')
68
- id = namer.get_name(id) if blank_node?(id)
100
+ ser_id = id = element.delete('@id')
101
+ if id.is_a?(Hash)
102
+ # Index graph using serialized id
103
+ ser_id = id.to_json_c14n
104
+ elsif id.nil?
105
+ ser_id = id = namer.get_name
106
+ end
69
107
 
70
- node = graph[id] ||= {'@id' => id}
108
+ node = graph[ser_id] ||= {'@id' => id}
71
109
 
72
- if active_subject.is_a?(Hash)
73
- # If subject is a hash, then we're processing a reverse-property relationship.
110
+ if reverse
111
+ # Note: active_subject is a Hash
112
+ # We're processing a reverse-property relationship.
74
113
  add_value(node, active_property, active_subject, property_is_array: true, allow_duplicate: false)
75
114
  elsif active_property
76
115
  reference = {'@id' => id}
@@ -81,7 +120,30 @@ module JSON::LD
81
120
  end
82
121
  end
83
122
 
84
- if element.has_key?('@type')
123
+ # For rdfstar, if node contains an `@annotation` member ...
124
+ # note: active_subject will not be nil, and may be an object itself.
125
+ # XXX: what if we're reversing an annotation?
126
+ if element.key?('@annotation')
127
+ # rdfstar being true is implicit, as it is checked in expansion
128
+ as = node_reference?(active_subject) ?
129
+ active_subject['@id'] :
130
+ active_subject
131
+ star_subject = reverse ?
132
+ {"@id" => node['@id'], active_property => [{'@id' => as}]} :
133
+ {"@id" => as, active_property => [{'@id' => node['@id']}]}
134
+
135
+ # Note that annotation is an array, make the reified subject the id of each member of that array.
136
+ annotation = element.delete('@annotation').map do |a|
137
+ a.merge('@id' => star_subject)
138
+ end
139
+
140
+ # Invoke recursively using annotation.
141
+ create_node_map(annotation, graph_map,
142
+ active_graph: active_graph,
143
+ active_subject: star_subject)
144
+ end
145
+
146
+ if element.key?('@type')
85
147
  add_value(node, '@type', element.delete('@type'), property_is_array: true, allow_duplicate: false)
86
148
  end
87
149
 
@@ -99,7 +161,8 @@ module JSON::LD
99
161
  create_node_map(value, graph_map,
100
162
  active_graph: active_graph,
101
163
  active_subject: referenced_node,
102
- active_property: property)
164
+ active_property: property,
165
+ reverse: true)
103
166
  end
104
167
  end
105
168
  end
@@ -128,7 +191,27 @@ module JSON::LD
128
191
  end
129
192
  end
130
193
 
194
+ ##
195
+ # Rename blank nodes recursively within an embedded object
196
+ #
197
+ # @param [Object] node
198
+ # @return [Hash]
199
+ def rename_bnodes(node)
200
+ case node
201
+ when Array
202
+ node.map {|n| rename_bnodes(n)}
203
+ when Hash
204
+ node.inject({}) do |memo, (k, v)|
205
+ v = namer.get_name(v) if k == '@id' && v.is_a?(String) && blank_node?(v)
206
+ memo.merge(k => rename_bnodes(v))
207
+ end
208
+ else
209
+ node
210
+ end
211
+ end
212
+
131
213
  private
214
+
132
215
  ##
133
216
  # Merge nodes from all graphs in the graph_map into a new node map
134
217
  #
@@ -57,7 +57,7 @@ module JSON::LD
57
57
  lambda: ->(files, **options) do
58
58
  out = options[:output] || $stdout
59
59
  out.set_encoding(Encoding::UTF_8) if RUBY_PLATFORM == "java"
60
- options = options.merge(expandContext: options.delete(:context)) if options.has_key?(:context)
60
+ options = options.merge(expandContext: options.delete(:context)) if options.key?(:context)
61
61
  options[:base] ||= options[:base_uri]
62
62
  if options[:format] == :jsonld
63
63
  if files.empty?
data/lib/json/ld/frame.rb CHANGED
@@ -52,7 +52,7 @@ module JSON::LD
52
52
  state[:uniqueEmbeds][state[:graph]] ||= {}
53
53
  end
54
54
 
55
- if flags[:embed] == '@link' && link.has_key?(id)
55
+ if flags[:embed] == '@link' && link.key?(id)
56
56
  # add existing linked subject
57
57
  add_frame_output(parent, property, link[id])
58
58
  next
@@ -66,7 +66,7 @@ module JSON::LD
66
66
  warn "[DEPRECATION] #{flags[:embed]} is not a valid value of @embed in 1.1 mode.\n"
67
67
  end
68
68
 
69
- if !state[:embedded] && state[:uniqueEmbeds][state[:graph]].has_key?(id)
69
+ if !state[:embedded] && state[:uniqueEmbeds][state[:graph]].key?(id)
70
70
  # Skip adding this node object to the top-level, as it was included in another node object
71
71
  next
72
72
  elsif state[:embedded] &&
@@ -76,7 +76,7 @@ module JSON::LD
76
76
  next
77
77
  elsif state[:embedded] &&
78
78
  %w(@first @once).include?(flags[:embed]) &&
79
- state[:uniqueEmbeds][state[:graph]].has_key?(id)
79
+ state[:uniqueEmbeds][state[:graph]].key?(id)
80
80
 
81
81
  # if only the first match should be embedded
82
82
  # Embed unless already embedded
@@ -97,7 +97,7 @@ module JSON::LD
97
97
  state[:subjectStack] << {subject: subject, graph: state[:graph]}
98
98
 
99
99
  # Subject is also the name of a graph
100
- if state[:graphMap].has_key?(id)
100
+ if state[:graphMap].key?(id)
101
101
  # check frame's "@graph" to see what to do next
102
102
  # 1. if it doesn't exist and state.graph === "@merged", don't recurse
103
103
  # 2. if it doesn't exist and state.graph !== "@merged", recurse
@@ -105,7 +105,7 @@ module JSON::LD
105
105
  # 4. if "@default" then don't recurse
106
106
  # 5. recurse
107
107
  recurse, subframe = false, nil
108
- if !frame.has_key?('@graph')
108
+ if !frame.key?('@graph')
109
109
  recurse, subframe = (state[:graph] != '@merged'), {}
110
110
  else
111
111
  subframe = frame['@graph'].first
@@ -134,7 +134,7 @@ module JSON::LD
134
134
  end
135
135
 
136
136
  # explicit is on and property isn't in frame, skip processing
137
- next if flags[:explicit] && !frame.has_key?(prop)
137
+ next if flags[:explicit] && !frame.key?(prop)
138
138
 
139
139
  # add objects
140
140
  objects.each do |o|
@@ -267,7 +267,7 @@ module JSON::LD
267
267
  # If, after replacement, an array contains only the value null remove the value, leaving an empty array.
268
268
  input.map {|o| cleanup_preserve(o)}
269
269
  when Hash
270
- if input.has_key?('@preserve')
270
+ if input.key?('@preserve')
271
271
  # Replace with the content of `@preserve`
272
272
  cleanup_preserve(input['@preserve'].first)
273
273
  else
@@ -388,7 +388,7 @@ module JSON::LD
388
388
  is_empty = v.empty?
389
389
  if v = v.first
390
390
  validate_frame(v)
391
- has_default = v.has_key?('@default')
391
+ has_default = v.key?('@default')
392
392
  end
393
393
 
394
394
  # No longer a wildcard pattern if frame has any non-keyword properties
@@ -75,7 +75,7 @@ module JSON::LD
75
75
  property: statement.predicate.to_s,
76
76
  value: value
77
77
  })
78
- elsif referenced_once.has_key?(statement.object.to_s)
78
+ elsif referenced_once.key?(statement.object.to_s)
79
79
  referenced_once[statement.object.to_s] = false
80
80
  elsif statement.object.node?
81
81
  referenced_once[statement.object.to_s] = {
@@ -146,7 +146,7 @@ module JSON::LD
146
146
  result = []
147
147
  default_graph.keys.opt_sort(ordered: @options[:ordered]).each do |subject|
148
148
  node = default_graph[subject]
149
- if graph_map.has_key?(subject)
149
+ if graph_map.key?(subject)
150
150
  node['@graph'] = []
151
151
  graph_map[subject].keys.opt_sort(ordered: @options[:ordered]).each do |s|
152
152
  n = graph_map[subject][s]
@@ -68,6 +68,7 @@ module JSON::LD
68
68
  # @raise [RDF::ReaderError] if the JSON document cannot be loaded
69
69
  def initialize(input = $stdin, **options, &block)
70
70
  options[:base_uri] ||= options[:base]
71
+ options[:rename_bnodes] = false unless options.key?(:rename_bnodes)
71
72
  super do
72
73
  @options[:base] ||= base_uri.to_s if base_uri
73
74
  # Trim non-JSON stuff in script.
@@ -136,7 +136,7 @@ module JSON::LD
136
136
  when '@type'
137
137
  # Set the type-scoped context to the context on input, for use later
138
138
  raise JsonLdError::InvalidStreamingKeyOrder,
139
- "found #{key} in state #{state}" unless [:await_context, :await_type].include?(state)
139
+ "found #{key} in state #{state}" unless %i(await_context await_type).include?(state)
140
140
 
141
141
  type_scoped_context = context
142
142
  as_array(value).sort.each do |term|
@@ -159,7 +159,7 @@ module JSON::LD
159
159
  raise JsonLdError::InvalidSetOrListObject,
160
160
  "found #{key} in state #{state}" if is_list_or_set
161
161
  raise JsonLdError::CollidingKeywords,
162
- "found #{key} in state #{state}" unless [:await_context, :await_type, :await_id].include?(state)
162
+ "found #{key} in state #{state}" unless %i(await_context await_type await_id).include?(state)
163
163
 
164
164
  # Set our actual id, and use for replacing any provisional statements using our existing node_id, which is provisional
165
165
  raise JsonLdError::InvalidIdValue,
@@ -209,7 +209,7 @@ module JSON::LD
209
209
  # Expanded values must be node objects
210
210
  have_statements = false
211
211
  parse_object(value, active_property, context) do |st|
212
- have_statements ||= st.has_subject?
212
+ have_statements ||= st.subject?
213
213
  block.call(st)
214
214
  end
215
215
  raise JsonLdError::InvalidIncludedValue, "values of @included must expand to node objects" unless have_statements
@@ -232,7 +232,7 @@ module JSON::LD
232
232
  when '@list'
233
233
  raise JsonLdError::InvalidSetOrListObject,
234
234
  "found #{key} in state #{state}" if
235
- ![:await_context, :await_type, :await_id].include?(state)
235
+ !%i(await_context await_type await_id).include?(state)
236
236
  is_list_or_set = true
237
237
  if subject
238
238
  node_id = parse_list(value, active_property, context, &block)
@@ -277,7 +277,7 @@ module JSON::LD
277
277
  when '@set'
278
278
  raise JsonLdError::InvalidSetOrListObject,
279
279
  "found #{key} in state #{state}" if
280
- ![:await_context, :await_type, :await_id].include?(state)
280
+ !%i(await_context await_type await_id).include?(state)
281
281
  is_list_or_set = true
282
282
  value = as_array(value).compact
283
283
  parse_object(value, active_property, context, subject: subject, predicate: predicate, &block)