json-ld 3.2.4 → 3.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/lib/json/ld/api.rb +807 -771
  4. data/lib/json/ld/compact.rb +304 -304
  5. data/lib/json/ld/conneg.rb +179 -161
  6. data/lib/json/ld/context.rb +2080 -1913
  7. data/lib/json/ld/expand.rb +745 -666
  8. data/lib/json/ld/extensions.rb +14 -13
  9. data/lib/json/ld/flatten.rb +257 -247
  10. data/lib/json/ld/format.rb +202 -194
  11. data/lib/json/ld/frame.rb +525 -502
  12. data/lib/json/ld/from_rdf.rb +223 -204
  13. data/lib/json/ld/html/nokogiri.rb +123 -121
  14. data/lib/json/ld/html/rexml.rb +151 -147
  15. data/lib/json/ld/reader.rb +107 -100
  16. data/lib/json/ld/resource.rb +224 -205
  17. data/lib/json/ld/streaming_reader.rb +574 -507
  18. data/lib/json/ld/streaming_writer.rb +93 -92
  19. data/lib/json/ld/to_rdf.rb +171 -169
  20. data/lib/json/ld/utils.rb +270 -264
  21. data/lib/json/ld/version.rb +24 -14
  22. data/lib/json/ld/writer.rb +334 -311
  23. data/lib/json/ld.rb +103 -96
  24. metadata +55 -198
  25. data/spec/api_spec.rb +0 -132
  26. data/spec/compact_spec.rb +0 -3482
  27. data/spec/conneg_spec.rb +0 -373
  28. data/spec/context_spec.rb +0 -2056
  29. data/spec/expand_spec.rb +0 -4496
  30. data/spec/flatten_spec.rb +0 -1203
  31. data/spec/format_spec.rb +0 -115
  32. data/spec/frame_spec.rb +0 -2541
  33. data/spec/from_rdf_spec.rb +0 -1072
  34. data/spec/matchers.rb +0 -20
  35. data/spec/rdfstar_spec.rb +0 -25
  36. data/spec/reader_spec.rb +0 -883
  37. data/spec/resource_spec.rb +0 -76
  38. data/spec/spec_helper.rb +0 -281
  39. data/spec/streaming_reader_spec.rb +0 -237
  40. data/spec/streaming_writer_spec.rb +0 -145
  41. data/spec/suite_compact_spec.rb +0 -22
  42. data/spec/suite_expand_spec.rb +0 -36
  43. data/spec/suite_flatten_spec.rb +0 -34
  44. data/spec/suite_frame_spec.rb +0 -29
  45. data/spec/suite_from_rdf_spec.rb +0 -22
  46. data/spec/suite_helper.rb +0 -411
  47. data/spec/suite_html_spec.rb +0 -22
  48. data/spec/suite_http_spec.rb +0 -35
  49. data/spec/suite_remote_doc_spec.rb +0 -22
  50. data/spec/suite_to_rdf_spec.rb +0 -30
  51. data/spec/support/extensions.rb +0 -44
  52. data/spec/test-files/test-1-compacted.jsonld +0 -10
  53. data/spec/test-files/test-1-context.jsonld +0 -7
  54. data/spec/test-files/test-1-expanded.jsonld +0 -5
  55. data/spec/test-files/test-1-input.jsonld +0 -10
  56. data/spec/test-files/test-1-rdf.ttl +0 -8
  57. data/spec/test-files/test-2-compacted.jsonld +0 -20
  58. data/spec/test-files/test-2-context.jsonld +0 -7
  59. data/spec/test-files/test-2-expanded.jsonld +0 -16
  60. data/spec/test-files/test-2-input.jsonld +0 -20
  61. data/spec/test-files/test-2-rdf.ttl +0 -14
  62. data/spec/test-files/test-3-compacted.jsonld +0 -11
  63. data/spec/test-files/test-3-context.jsonld +0 -8
  64. data/spec/test-files/test-3-expanded.jsonld +0 -10
  65. data/spec/test-files/test-3-input.jsonld +0 -11
  66. data/spec/test-files/test-3-rdf.ttl +0 -8
  67. data/spec/test-files/test-4-compacted.jsonld +0 -10
  68. data/spec/test-files/test-4-context.jsonld +0 -7
  69. data/spec/test-files/test-4-expanded.jsonld +0 -6
  70. data/spec/test-files/test-4-input.jsonld +0 -10
  71. data/spec/test-files/test-4-rdf.ttl +0 -5
  72. data/spec/test-files/test-5-compacted.jsonld +0 -13
  73. data/spec/test-files/test-5-context.jsonld +0 -7
  74. data/spec/test-files/test-5-expanded.jsonld +0 -9
  75. data/spec/test-files/test-5-input.jsonld +0 -13
  76. data/spec/test-files/test-5-rdf.ttl +0 -7
  77. data/spec/test-files/test-6-compacted.jsonld +0 -10
  78. data/spec/test-files/test-6-context.jsonld +0 -7
  79. data/spec/test-files/test-6-expanded.jsonld +0 -10
  80. data/spec/test-files/test-6-input.jsonld +0 -10
  81. data/spec/test-files/test-6-rdf.ttl +0 -6
  82. data/spec/test-files/test-7-compacted.jsonld +0 -23
  83. data/spec/test-files/test-7-context.jsonld +0 -4
  84. data/spec/test-files/test-7-expanded.jsonld +0 -20
  85. data/spec/test-files/test-7-input.jsonld +0 -23
  86. data/spec/test-files/test-7-rdf.ttl +0 -14
  87. data/spec/test-files/test-8-compacted.jsonld +0 -34
  88. data/spec/test-files/test-8-context.jsonld +0 -11
  89. data/spec/test-files/test-8-expanded.jsonld +0 -24
  90. data/spec/test-files/test-8-frame.jsonld +0 -18
  91. data/spec/test-files/test-8-framed.jsonld +0 -25
  92. data/spec/test-files/test-8-input.jsonld +0 -30
  93. data/spec/test-files/test-8-rdf.ttl +0 -15
  94. data/spec/test-files/test-9-compacted.jsonld +0 -20
  95. data/spec/test-files/test-9-context.jsonld +0 -13
  96. data/spec/test-files/test-9-expanded.jsonld +0 -14
  97. data/spec/test-files/test-9-input.jsonld +0 -12
  98. data/spec/to_rdf_spec.rb +0 -1684
  99. data/spec/writer_spec.rb +0 -427
@@ -1,42 +1,43 @@
1
- # -*- encoding: utf-8 -*-
2
1
  # frozen_string_literal: true
2
+
3
3
  module RDF
4
4
  class Node
5
5
  # Odd case of appending to a BNode identifier
6
- def +(value)
7
- Node.new(id + value.to_s)
6
+ def +(other)
7
+ Node.new(id + other.to_s)
8
8
  end
9
9
  end
10
10
 
11
11
  class Statement
12
12
  # Validate extended RDF
13
13
  def valid_extended?
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)
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
 
21
- class URI
21
+ class URI
22
22
  # Validate extended RDF
23
23
  def valid_extended?
24
- self.valid?
24
+ valid?
25
25
  end
26
26
  end
27
27
 
28
- class Node
28
+ class Node
29
29
  # Validate extended RDF
30
30
  def valid_extended?
31
- self.valid?
31
+ valid?
32
32
  end
33
33
  end
34
34
 
35
- class Literal
35
+ class Literal
36
36
  # Validate extended RDF
37
37
  def valid_extended?
38
38
  return false if language? && language.to_s !~ /^[a-zA-Z]+(-[a-zA-Z0-9]+)*$/
39
39
  return false if datatype? && datatype.invalid?
40
+
40
41
  value.is_a?(String)
41
42
  end
42
43
  end
@@ -48,6 +49,6 @@ class Array
48
49
  # @param [Boolean] ordered
49
50
  # @return [Array]
50
51
  def opt_sort(ordered: false)
51
- ordered ? self.sort : self
52
+ ordered ? sort : self
52
53
  end
53
54
  end
@@ -1,289 +1,299 @@
1
- # -*- encoding: utf-8 -*-
2
1
  # frozen_string_literal: true
2
+
3
3
  require 'json/canonicalization'
4
4
 
5
- module JSON::LD
6
- module Flatten
7
- include Utils
8
-
9
- ##
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.
11
- #
12
- # For RDF-star/JSON-LD-star:
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
- #
16
- # @param [Array, Hash] element
17
- # Expanded JSON-LD input
18
- # @param [Hash] graph_map A map of graph name to subjects
19
- # @param [String] active_graph
20
- # The name of the currently active graph that the processor should use when processing.
21
- # @param [String] active_subject (nil)
22
- # Node identifier
23
- # @param [String] active_property (nil)
24
- # Property within current node
25
- # @param [Boolean] reverse (false)
26
- # Processing a reverse relationship
27
- # @param [Array] list (nil)
28
- # Used when property value is a list
29
- def create_node_map(element, graph_map,
30
- active_graph: '@default',
31
- active_subject: nil,
32
- active_property: nil,
33
- reverse: false,
34
- list: nil)
35
- if element.is_a?(Array)
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.
37
- element.map do |o|
38
- create_node_map(o, graph_map,
39
- active_graph: active_graph,
40
- active_subject: active_subject,
41
- active_property: active_property,
5
+ module JSON
6
+ module LD
7
+ module Flatten
8
+ include Utils
9
+
10
+ ##
11
+ # 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.
12
+ #
13
+ # For RDF-star/JSON-LD-star:
14
+ # * 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.
15
+ # * The presence of `@annotation` implies an embedded node and the annotation object is removed from the node/value object in which it appears.
16
+ #
17
+ # @param [Array, Hash] element
18
+ # Expanded JSON-LD input
19
+ # @param [Hash] graph_map A map of graph name to subjects
20
+ # @param [String] active_graph
21
+ # The name of the currently active graph that the processor should use when processing.
22
+ # @param [String] active_subject (nil)
23
+ # Node identifier
24
+ # @param [String] active_property (nil)
25
+ # Property within current node
26
+ # @param [Boolean] reverse (false)
27
+ # Processing a reverse relationship
28
+ # @param [Array] list (nil)
29
+ # Used when property value is a list
30
+ def create_node_map(element, graph_map,
31
+ active_graph: '@default',
32
+ active_subject: nil,
33
+ active_property: nil,
42
34
  reverse: false,
43
- list: list)
44
- end
45
- elsif !element.is_a?(Hash)
46
- raise "Expected hash or array to create_node_map, got #{element.inspect}"
47
- else
48
- graph = (graph_map[active_graph] ||= {})
49
- subject_node = !reverse && graph[active_subject.is_a?(Hash) ? active_subject.to_json_c14n : active_subject]
50
-
51
- # Transform BNode types
52
- if element.key?('@type')
53
- element['@type'] = Array(element['@type']).map {|t| blank_node?(t) ? namer.get_name(t) : t}
54
- end
55
-
56
- if value?(element)
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
-
81
- if list.nil?
82
- add_value(subject_node, active_property, element, property_is_array: true, allow_duplicate: false)
83
- else
84
- list['@list'] << element
85
- end
86
- elsif list?(element)
87
- result = {'@list' => []}
88
- create_node_map(element['@list'], graph_map,
89
- active_graph: active_graph,
90
- active_subject: active_subject,
91
- active_property: active_property,
92
- list: result)
93
- if list.nil?
94
- add_value(subject_node, active_property, result, property_is_array: true)
95
- else
96
- list['@list'] << result
35
+ list: nil)
36
+ if element.is_a?(Array)
37
+ # 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.
38
+ element.map do |o|
39
+ create_node_map(o, graph_map,
40
+ active_graph: active_graph,
41
+ active_subject: active_subject,
42
+ active_property: active_property,
43
+ reverse: false,
44
+ list: list)
97
45
  end
46
+ elsif !element.is_a?(Hash)
47
+ raise "Expected hash or array to create_node_map, got #{element.inspect}"
98
48
  else
99
- # Element is a node object
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
49
+ graph = (graph_map[active_graph] ||= {})
50
+ subject_node = !reverse && graph[active_subject.is_a?(Hash) ? active_subject.to_json_c14n : active_subject]
51
+
52
+ # Transform BNode types
53
+ if element.key?('@type')
54
+ element['@type'] = Array(element['@type']).map { |t| blank_node?(t) ? namer.get_name(t) : t }
106
55
  end
107
56
 
108
- node = graph[ser_id] ||= {'@id' => id}
57
+ if value?(element)
58
+ element['@type'] = element['@type'].first if element['@type']
59
+
60
+ # For rdfstar, if value contains an `@annotation` member ...
61
+ # note: active_subject will not be nil, and may be an object itself.
62
+ if element.key?('@annotation')
63
+ # rdfstar being true is implicit, as it is checked in expansion
64
+ as = if node_reference?(active_subject)
65
+ active_subject['@id']
66
+ else
67
+ active_subject
68
+ end
69
+ star_subject = {
70
+ "@id" => as,
71
+ active_property => [element]
72
+ }
73
+
74
+ # Note that annotation is an array, make the reified subject the id of each member of that array.
75
+ annotation = element.delete('@annotation').map do |a|
76
+ a.merge('@id' => star_subject)
77
+ end
78
+
79
+ # Invoke recursively using annotation.
80
+ create_node_map(annotation, graph_map,
81
+ active_graph: active_graph)
82
+ end
109
83
 
110
- if reverse
111
- # Note: active_subject is a Hash
112
- # We're processing a reverse-property relationship.
113
- add_value(node, active_property, active_subject, property_is_array: true, allow_duplicate: false)
114
- elsif active_property
115
- reference = {'@id' => id}
116
84
  if list.nil?
117
- add_value(subject_node, active_property, reference, property_is_array: true, allow_duplicate: false)
85
+ add_value(subject_node, active_property, element, property_is_array: true, allow_duplicate: false)
118
86
  else
119
- list['@list'] << reference
87
+ list['@list'] << element
88
+ end
89
+ elsif list?(element)
90
+ result = { '@list' => [] }
91
+ create_node_map(element['@list'], graph_map,
92
+ active_graph: active_graph,
93
+ active_subject: active_subject,
94
+ active_property: active_property,
95
+ list: result)
96
+ if list.nil?
97
+ add_value(subject_node, active_property, result, property_is_array: true)
98
+ else
99
+ list['@list'] << result
100
+ end
101
+ else
102
+ # Element is a node object
103
+ ser_id = id = element.delete('@id')
104
+ if id.is_a?(Hash)
105
+ # Index graph using serialized id
106
+ ser_id = id.to_json_c14n
107
+ elsif id.nil?
108
+ ser_id = id = namer.get_name
120
109
  end
121
- end
122
110
 
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)
111
+ node = graph[ser_id] ||= { '@id' => id }
112
+
113
+ if reverse
114
+ # NOTE: active_subject is a Hash
115
+ # We're processing a reverse-property relationship.
116
+ add_value(node, active_property, active_subject, property_is_array: true, allow_duplicate: false)
117
+ elsif active_property
118
+ reference = { '@id' => id }
119
+ if list.nil?
120
+ add_value(subject_node, active_property, reference, property_is_array: true, allow_duplicate: false)
121
+ else
122
+ list['@list'] << reference
123
+ end
138
124
  end
139
125
 
140
- # Invoke recursively using annotation.
141
- create_node_map(annotation, graph_map,
142
- active_graph: active_graph,
143
- active_subject: star_subject)
144
- end
126
+ # For rdfstar, if node contains an `@annotation` member ...
127
+ # note: active_subject will not be nil, and may be an object itself.
128
+ # XXX: what if we're reversing an annotation?
129
+ if element.key?('@annotation')
130
+ # rdfstar being true is implicit, as it is checked in expansion
131
+ as = if node_reference?(active_subject)
132
+ active_subject['@id']
133
+ else
134
+ active_subject
135
+ end
136
+ star_subject = if reverse
137
+ { "@id" => node['@id'], active_property => [{ '@id' => as }] }
138
+ else
139
+ { "@id" => as, active_property => [{ '@id' => node['@id'] }] }
140
+ end
145
141
 
146
- if element.key?('@type')
147
- add_value(node, '@type', element.delete('@type'), property_is_array: true, allow_duplicate: false)
148
- end
142
+ # Note that annotation is an array, make the reified subject the id of each member of that array.
143
+ annotation = element.delete('@annotation').map do |a|
144
+ a.merge('@id' => star_subject)
145
+ end
149
146
 
150
- if element['@index']
151
- raise JsonLdError::ConflictingIndexes,
152
- "Element already has index #{node['@index']} dfferent from #{element['@index']}" if
153
- node.key?('@index') && node['@index'] != element['@index']
154
- node['@index'] = element.delete('@index')
155
- end
147
+ # Invoke recursively using annotation.
148
+ create_node_map(annotation, graph_map,
149
+ active_graph: active_graph,
150
+ active_subject: star_subject)
151
+ end
156
152
 
157
- if element['@reverse']
158
- referenced_node, reverse_map = {'@id' => id}, element.delete('@reverse')
159
- reverse_map.each do |property, values|
160
- values.each do |value|
161
- create_node_map(value, graph_map,
162
- active_graph: active_graph,
163
- active_subject: referenced_node,
164
- active_property: property,
165
- reverse: true)
153
+ if element.key?('@type')
154
+ add_value(node, '@type', element.delete('@type'), property_is_array: true, allow_duplicate: false)
155
+ end
156
+
157
+ if element['@index']
158
+ if node.key?('@index') && node['@index'] != element['@index']
159
+ raise JsonLdError::ConflictingIndexes,
160
+ "Element already has index #{node['@index']} dfferent from #{element['@index']}"
166
161
  end
162
+ node['@index'] = element.delete('@index')
167
163
  end
168
- end
169
164
 
170
- if element['@graph']
171
- create_node_map(element.delete('@graph'), graph_map,
172
- active_graph: id)
173
- end
165
+ if element['@reverse']
166
+ referenced_node = { '@id' => id }
167
+ reverse_map = element.delete('@reverse')
168
+ reverse_map.each do |property, values|
169
+ values.each do |value|
170
+ create_node_map(value, graph_map,
171
+ active_graph: active_graph,
172
+ active_subject: referenced_node,
173
+ active_property: property,
174
+ reverse: true)
175
+ end
176
+ end
177
+ end
174
178
 
175
- if element['@included']
176
- create_node_map(element.delete('@included'), graph_map,
177
- active_graph: active_graph)
178
- end
179
+ if element['@graph']
180
+ create_node_map(element.delete('@graph'), graph_map,
181
+ active_graph: id)
182
+ end
183
+
184
+ if element['@included']
185
+ create_node_map(element.delete('@included'), graph_map,
186
+ active_graph: active_graph)
187
+ end
179
188
 
180
- element.keys.each do |property|
181
- value = element[property]
189
+ element.each_key do |property|
190
+ value = element[property]
182
191
 
183
- property = namer.get_name(property) if blank_node?(property)
184
- node[property] ||= []
185
- create_node_map(value, graph_map,
186
- active_graph: active_graph,
187
- active_subject: id,
188
- active_property: property)
192
+ property = namer.get_name(property) if blank_node?(property)
193
+ node[property] ||= []
194
+ create_node_map(value, graph_map,
195
+ active_graph: active_graph,
196
+ active_subject: id,
197
+ active_property: property)
198
+ end
189
199
  end
190
200
  end
191
201
  end
192
- end
193
202
 
194
- ##
195
- # Create annotations
196
- #
197
- # Updates a node map from which annotations have been folded into embedded triples to re-extract the annotations.
198
- #
199
- # Map entries where the key is of the form of a canonicalized JSON object are used to find keys with the `@id` and property components. If found, the original map entry is removed and entries added to an `@annotation` property of the associated value.
200
- #
201
- # * Keys which are of the form of a canonicalized JSON object are examined in inverse order of length.
202
- # * Deserialize the key into a map, and re-serialize the value of `@id`.
203
- # * If the map contains an entry with that value (after re-canonicalizing, as appropriate), and the associated antry has a item which matches the non-`@id` item from the map, the node is used to create an `@annotation` entry within that value.
204
- #
205
- # @param [Hash{String => Hash}] node_map
206
- # @return [Hash{String => Hash}]
207
- def create_annotations(node_map)
208
- node_map.keys.
209
- select {|k| k.start_with?('{')}.
210
- sort_by(&:length).
211
- reverse.each do |key|
212
-
213
- annotation = node_map[key]
214
- # Deserialize key, and re-serialize the `@id` value.
215
- emb = annotation['@id'].dup
216
- id = emb.delete('@id')
217
- property, value = emb.to_a.first
218
-
219
- # If id is a map, set it to the result of canonicalizing that value, otherwise to itself.
220
- id = id.to_json_c14n if id.is_a?(Hash)
221
-
222
- next unless node_map.key?(id)
223
- # If node map has an entry for id and that entry contains the same property and value from entry:
224
- node = node_map[id]
225
-
226
- next unless node.key?(property)
227
-
228
- node[property].each do |emb_value|
229
- next unless emb_value == value.first
230
-
231
- node_map.delete(key)
232
- annotation.delete('@id')
233
- add_value(emb_value, '@annotation', annotation, property_is_array: true) unless
234
- annotation.empty?
203
+ ##
204
+ # Create annotations
205
+ #
206
+ # Updates a node map from which annotations have been folded into embedded triples to re-extract the annotations.
207
+ #
208
+ # Map entries where the key is of the form of a canonicalized JSON object are used to find keys with the `@id` and property components. If found, the original map entry is removed and entries added to an `@annotation` property of the associated value.
209
+ #
210
+ # * Keys which are of the form of a canonicalized JSON object are examined in inverse order of length.
211
+ # * Deserialize the key into a map, and re-serialize the value of `@id`.
212
+ # * If the map contains an entry with that value (after re-canonicalizing, as appropriate), and the associated antry has a item which matches the non-`@id` item from the map, the node is used to create an `@annotation` entry within that value.
213
+ #
214
+ # @param [Hash{String => Hash}] node_map
215
+ # @return [Hash{String => Hash}]
216
+ def create_annotations(node_map)
217
+ node_map.keys
218
+ .select { |k| k.start_with?('{') }
219
+ .sort_by(&:length)
220
+ .reverse_each do |key|
221
+ annotation = node_map[key]
222
+ # Deserialize key, and re-serialize the `@id` value.
223
+ emb = annotation['@id'].dup
224
+ id = emb.delete('@id')
225
+ property, value = emb.to_a.first
226
+
227
+ # If id is a map, set it to the result of canonicalizing that value, otherwise to itself.
228
+ id = id.to_json_c14n if id.is_a?(Hash)
229
+
230
+ next unless node_map.key?(id)
231
+
232
+ # If node map has an entry for id and that entry contains the same property and value from entry:
233
+ node = node_map[id]
234
+
235
+ next unless node.key?(property)
236
+
237
+ node[property].each do |emb_value|
238
+ next unless emb_value == value.first
239
+
240
+ node_map.delete(key)
241
+ annotation.delete('@id')
242
+ add_value(emb_value, '@annotation', annotation, property_is_array: true) unless
243
+ annotation.empty?
244
+ end
235
245
  end
236
246
  end
237
- end
238
247
 
239
- ##
240
- # Rename blank nodes recursively within an embedded object
241
- #
242
- # @param [Object] node
243
- # @return [Hash]
244
- def rename_bnodes(node)
245
- case node
246
- when Array
247
- node.map {|n| rename_bnodes(n)}
248
- when Hash
249
- node.inject({}) do |memo, (k, v)|
250
- v = namer.get_name(v) if k == '@id' && v.is_a?(String) && blank_node?(v)
251
- memo.merge(k => rename_bnodes(v))
248
+ ##
249
+ # Rename blank nodes recursively within an embedded object
250
+ #
251
+ # @param [Object] node
252
+ # @return [Hash]
253
+ def rename_bnodes(node)
254
+ case node
255
+ when Array
256
+ node.map { |n| rename_bnodes(n) }
257
+ when Hash
258
+ node.each_with_object({}) do |(k, v), memo|
259
+ v = namer.get_name(v) if k == '@id' && v.is_a?(String) && blank_node?(v)
260
+ memo[k] = rename_bnodes(v)
261
+ end
262
+ else
263
+ node
252
264
  end
253
- else
254
- node
255
265
  end
256
- end
257
266
 
258
- private
259
-
260
- ##
261
- # Merge nodes from all graphs in the graph_map into a new node map
262
- #
263
- # @param [Hash{String => Hash}] graph_map
264
- # @return [Hash]
265
- def merge_node_map_graphs(graph_map)
266
- merged = {}
267
- graph_map.each do |name, node_map|
268
- node_map.each do |id, node|
269
- merged_node = (merged[id] ||= {'@id' => id})
270
-
271
- # Iterate over node properties
272
- node.each do |property, values|
273
- if property != '@type' && property.start_with?('@')
274
- # Copy keywords
275
- merged_node[property] = node[property].dup
276
- else
277
- # Merge objects
278
- values.each do |value|
279
- add_value(merged_node, property, value.dup, property_is_array: true)
267
+ private
268
+
269
+ ##
270
+ # Merge nodes from all graphs in the graph_map into a new node map
271
+ #
272
+ # @param [Hash{String => Hash}] graph_map
273
+ # @return [Hash]
274
+ def merge_node_map_graphs(graph_map)
275
+ merged = {}
276
+ graph_map.each do |_name, node_map|
277
+ node_map.each do |id, node|
278
+ merged_node = (merged[id] ||= { '@id' => id })
279
+
280
+ # Iterate over node properties
281
+ node.each do |property, values|
282
+ if property != '@type' && property.start_with?('@')
283
+ # Copy keywords
284
+ merged_node[property] = node[property].dup
285
+ else
286
+ # Merge objects
287
+ values.each do |value|
288
+ add_value(merged_node, property, value.dup, property_is_array: true)
289
+ end
280
290
  end
281
291
  end
282
292
  end
283
293
  end
284
- end
285
294
 
286
- merged
295
+ merged
296
+ end
287
297
  end
288
298
  end
289
299
  end