json-ld 3.2.3 → 3.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/json/ld/api.rb +807 -764
- data/lib/json/ld/compact.rb +304 -304
- data/lib/json/ld/conneg.rb +179 -161
- data/lib/json/ld/context.rb +2080 -1945
- data/lib/json/ld/expand.rb +745 -666
- data/lib/json/ld/extensions.rb +14 -13
- data/lib/json/ld/flatten.rb +257 -247
- data/lib/json/ld/format.rb +202 -194
- data/lib/json/ld/frame.rb +525 -502
- data/lib/json/ld/from_rdf.rb +224 -166
- data/lib/json/ld/html/nokogiri.rb +123 -121
- data/lib/json/ld/html/rexml.rb +151 -147
- data/lib/json/ld/reader.rb +107 -100
- data/lib/json/ld/resource.rb +224 -205
- data/lib/json/ld/streaming_reader.rb +574 -507
- data/lib/json/ld/streaming_writer.rb +93 -92
- data/lib/json/ld/to_rdf.rb +171 -167
- data/lib/json/ld/utils.rb +270 -264
- data/lib/json/ld/version.rb +24 -14
- data/lib/json/ld/writer.rb +334 -311
- data/lib/json/ld.rb +103 -96
- metadata +78 -209
- data/spec/api_spec.rb +0 -132
- data/spec/compact_spec.rb +0 -3482
- data/spec/conneg_spec.rb +0 -373
- data/spec/context_spec.rb +0 -2036
- data/spec/expand_spec.rb +0 -4496
- data/spec/flatten_spec.rb +0 -1203
- data/spec/format_spec.rb +0 -115
- data/spec/frame_spec.rb +0 -2498
- data/spec/from_rdf_spec.rb +0 -1005
- data/spec/matchers.rb +0 -20
- data/spec/rdfstar_spec.rb +0 -25
- data/spec/reader_spec.rb +0 -883
- data/spec/resource_spec.rb +0 -76
- data/spec/spec_helper.rb +0 -281
- data/spec/streaming_reader_spec.rb +0 -237
- data/spec/streaming_writer_spec.rb +0 -145
- data/spec/suite_compact_spec.rb +0 -22
- data/spec/suite_expand_spec.rb +0 -36
- data/spec/suite_flatten_spec.rb +0 -34
- data/spec/suite_frame_spec.rb +0 -29
- data/spec/suite_from_rdf_spec.rb +0 -22
- data/spec/suite_helper.rb +0 -411
- data/spec/suite_html_spec.rb +0 -22
- data/spec/suite_http_spec.rb +0 -35
- data/spec/suite_remote_doc_spec.rb +0 -22
- data/spec/suite_to_rdf_spec.rb +0 -30
- data/spec/support/extensions.rb +0 -44
- data/spec/test-files/test-1-compacted.jsonld +0 -10
- data/spec/test-files/test-1-context.jsonld +0 -7
- data/spec/test-files/test-1-expanded.jsonld +0 -5
- data/spec/test-files/test-1-input.jsonld +0 -10
- data/spec/test-files/test-1-rdf.ttl +0 -8
- data/spec/test-files/test-2-compacted.jsonld +0 -20
- data/spec/test-files/test-2-context.jsonld +0 -7
- data/spec/test-files/test-2-expanded.jsonld +0 -16
- data/spec/test-files/test-2-input.jsonld +0 -20
- data/spec/test-files/test-2-rdf.ttl +0 -14
- data/spec/test-files/test-3-compacted.jsonld +0 -11
- data/spec/test-files/test-3-context.jsonld +0 -8
- data/spec/test-files/test-3-expanded.jsonld +0 -10
- data/spec/test-files/test-3-input.jsonld +0 -11
- data/spec/test-files/test-3-rdf.ttl +0 -8
- data/spec/test-files/test-4-compacted.jsonld +0 -10
- data/spec/test-files/test-4-context.jsonld +0 -7
- data/spec/test-files/test-4-expanded.jsonld +0 -6
- data/spec/test-files/test-4-input.jsonld +0 -10
- data/spec/test-files/test-4-rdf.ttl +0 -5
- data/spec/test-files/test-5-compacted.jsonld +0 -13
- data/spec/test-files/test-5-context.jsonld +0 -7
- data/spec/test-files/test-5-expanded.jsonld +0 -9
- data/spec/test-files/test-5-input.jsonld +0 -13
- data/spec/test-files/test-5-rdf.ttl +0 -7
- data/spec/test-files/test-6-compacted.jsonld +0 -10
- data/spec/test-files/test-6-context.jsonld +0 -7
- data/spec/test-files/test-6-expanded.jsonld +0 -10
- data/spec/test-files/test-6-input.jsonld +0 -10
- data/spec/test-files/test-6-rdf.ttl +0 -6
- data/spec/test-files/test-7-compacted.jsonld +0 -23
- data/spec/test-files/test-7-context.jsonld +0 -4
- data/spec/test-files/test-7-expanded.jsonld +0 -20
- data/spec/test-files/test-7-input.jsonld +0 -23
- data/spec/test-files/test-7-rdf.ttl +0 -14
- data/spec/test-files/test-8-compacted.jsonld +0 -34
- data/spec/test-files/test-8-context.jsonld +0 -11
- data/spec/test-files/test-8-expanded.jsonld +0 -24
- data/spec/test-files/test-8-frame.jsonld +0 -18
- data/spec/test-files/test-8-framed.jsonld +0 -25
- data/spec/test-files/test-8-input.jsonld +0 -30
- data/spec/test-files/test-8-rdf.ttl +0 -15
- data/spec/test-files/test-9-compacted.jsonld +0 -20
- data/spec/test-files/test-9-context.jsonld +0 -13
- data/spec/test-files/test-9-expanded.jsonld +0 -14
- data/spec/test-files/test-9-input.jsonld +0 -12
- data/spec/to_rdf_spec.rb +0 -1551
- data/spec/writer_spec.rb +0 -427
@@ -1,579 +1,646 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'json/ld'
|
3
4
|
require 'json/ld/expand'
|
4
5
|
require 'json/ld/to_rdf'
|
5
6
|
|
6
|
-
module JSON
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
7
|
+
module JSON
|
8
|
+
module LD
|
9
|
+
##
|
10
|
+
# A streaming JSON-LD parser in Ruby.
|
11
|
+
#
|
12
|
+
# @see http://json-ld.org/spec/ED/20110507/
|
13
|
+
# @author [Gregg Kellogg](http://greggkellogg.net/)
|
14
|
+
module StreamingReader
|
15
|
+
include Utils
|
16
|
+
include JSON::LD::ToRDF # For value object conversion
|
17
|
+
|
18
|
+
# The base URI to use when resolving relative URIs
|
19
|
+
# @return [RDF::URI]
|
20
|
+
attr_reader :base
|
21
|
+
attr_reader :namer
|
22
|
+
|
23
|
+
def self.format
|
24
|
+
JSON::LD::Format
|
25
|
+
end
|
20
26
|
|
21
|
-
|
27
|
+
##
|
28
|
+
# @see RDF::Reader#each_statement
|
29
|
+
def stream_statement
|
30
|
+
unique_bnodes = @options[:unique_bnodes]
|
31
|
+
rename_bnodes = @options.fetch(:rename_bnodes, true)
|
32
|
+
# FIXME: document loader doesn't stream
|
33
|
+
@base = RDF::URI(@options[:base] || base_uri)
|
34
|
+
mj_opts = @options.keep_if { |k, v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v) }
|
35
|
+
value = MultiJson.load(@doc, mj_opts)
|
36
|
+
context_ref = @options[:expandContext]
|
37
|
+
# context_ref = @options.fetch(:expandContext, remote_doc.contextUrl)
|
38
|
+
context = Context.parse(context_ref, **@options)
|
39
|
+
|
40
|
+
@namer = if unique_bnodes
|
41
|
+
BlankNodeUniqer.new
|
42
|
+
else
|
43
|
+
(rename_bnodes ? BlankNodeNamer.new("b") : BlankNodeMapper.new)
|
44
|
+
end
|
45
|
+
# Namer for naming provisional nodes, which may be determined later to be actual
|
46
|
+
@provisional_namer = BlankNodeNamer.new("p")
|
22
47
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
unique_bnodes, rename_bnodes = @options[:unique_bnodes], @options.fetch(:rename_bnodes, true)
|
27
|
-
# FIXME: document loader doesn't stream
|
28
|
-
@base = RDF::URI(@options[:base] || base_uri)
|
29
|
-
mj_opts = @options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
|
30
|
-
value = MultiJson.load(@doc, mj_opts)
|
31
|
-
context_ref = @options[:expandContext]
|
32
|
-
#context_ref = @options.fetch(:expandContext, remote_doc.contextUrl)
|
33
|
-
context = Context.parse(context_ref, **@options)
|
34
|
-
|
35
|
-
@namer = unique_bnodes ? BlankNodeUniqer.new : (rename_bnodes ? BlankNodeNamer.new("b") : BlankNodeMapper.new)
|
36
|
-
# Namer for naming provisional nodes, which may be determined later to be actual
|
37
|
-
@provisional_namer = BlankNodeNamer.new("p")
|
38
|
-
|
39
|
-
parse_object(value, nil, context, graph_is_named: false) do |st|
|
40
|
-
# Only output reasonably valid triples
|
41
|
-
if st.to_a.all? {|r| r.is_a?(RDF::Term) && (r.uri? ? r.valid? : true)}
|
42
|
-
block.call(st)
|
48
|
+
parse_object(value, nil, context, graph_is_named: false) do |st|
|
49
|
+
# Only output reasonably valid triples
|
50
|
+
yield(st) if st.to_a.all? { |r| r.is_a?(RDF::Term) && (r.uri? ? r.valid? : true) }
|
43
51
|
end
|
52
|
+
rescue ::JSON::ParserError, ::JSON::LD::JsonLdError => e
|
53
|
+
log_fatal("Failed to parse input document: #{e.message}", exception: RDF::ReaderError)
|
44
54
|
end
|
45
|
-
rescue ::JSON::ParserError, ::JSON::LD::JsonLdError => e
|
46
|
-
log_fatal("Failed to parse input document: #{e.message}", exception: RDF::ReaderError)
|
47
|
-
end
|
48
55
|
|
49
|
-
|
56
|
+
private
|
57
|
+
|
58
|
+
# Parse a node object, or array of node objects
|
59
|
+
#
|
60
|
+
# @param [Array, Hash] input
|
61
|
+
# @param [String] active_property
|
62
|
+
# The unexpanded property referencing this object
|
63
|
+
# @param [Context] context
|
64
|
+
# @param [RDF::Resource] subject referencing this object
|
65
|
+
# @param [RDF::URI] predicate the predicate part of the reference
|
66
|
+
# @param [Boolean] from_map
|
67
|
+
# Expanding from a map, which could be an `@type` map, so don't clear out context term definitions
|
68
|
+
# @param [Boolean] graph_is_named
|
69
|
+
# Use of `@graph` implies a named graph; not true at the top-level.
|
70
|
+
# @param [RDF::URI] extra_type from a type map
|
71
|
+
# @param [String] language from a language map
|
72
|
+
# @param [RDF::Resource] node_id from an id map
|
73
|
+
# @return [void]
|
74
|
+
def parse_object(input, active_property, context,
|
75
|
+
subject: nil, predicate: nil, from_map: false,
|
76
|
+
extra_type: nil, language: nil, node_id: nil,
|
77
|
+
graph_is_named: true, &block)
|
78
|
+
|
79
|
+
# Skip predicates that look like a BNode
|
80
|
+
if predicate.to_s.start_with?('_:')
|
81
|
+
warn "[DEPRECATION] Blank Node properties deprecated in JSON-LD 1.1."
|
82
|
+
return
|
83
|
+
end
|
50
84
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
# @param [RDF::URI] predicate the predicate part of the reference
|
59
|
-
# @param [Boolean] from_map
|
60
|
-
# Expanding from a map, which could be an `@type` map, so don't clear out context term definitions
|
61
|
-
# @param [Boolean] graph_is_named
|
62
|
-
# Use of `@graph` implies a named graph; not true at the top-level.
|
63
|
-
# @param [RDF::URI] extra_type from a type map
|
64
|
-
# @param [String] language from a language map
|
65
|
-
# @param [RDF::Resource] node_id from an id map
|
66
|
-
# @return [void]
|
67
|
-
def parse_object(input, active_property, context,
|
68
|
-
subject: nil, predicate: nil, from_map: false,
|
69
|
-
extra_type: nil, language: nil, node_id: nil,
|
70
|
-
graph_is_named: true, &block)
|
71
|
-
|
72
|
-
# Skip predicates that look like a BNode
|
73
|
-
if predicate.to_s.start_with?('_:')
|
74
|
-
warn "[DEPRECATION] Blank Node properties deprecated in JSON-LD 1.1."
|
75
|
-
return
|
76
|
-
end
|
85
|
+
if input.is_a?(Array)
|
86
|
+
input.each do |e|
|
87
|
+
parse_object(e, active_property, context, subject: subject, predicate: predicate, from_map: from_map,
|
88
|
+
&block)
|
89
|
+
end
|
90
|
+
return
|
91
|
+
end
|
77
92
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
93
|
+
# Note that we haven't parsed an @id key, so have no subject
|
94
|
+
have_id = false
|
95
|
+
node_reference = false
|
96
|
+
is_list_or_set = false
|
97
|
+
node_id ||= RDF::Node.new(@provisional_namer.get_sym)
|
98
|
+
# For keeping statements not yet ready to be emitted
|
99
|
+
provisional_statements = []
|
100
|
+
value_object = {}
|
101
|
+
|
102
|
+
# Use a term-specific context, if defined, based on the non-type-scoped context.
|
103
|
+
if active_property && context.term_definitions[active_property]
|
104
|
+
property_scoped_context = context.term_definitions[active_property].context
|
105
|
+
end
|
82
106
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
# Revert any previously type-scoped term definitions, unless this is from a map, a value object or a subject reference
|
94
|
-
# FIXME
|
95
|
-
if input.is_a?(Hash) && context.previous_context
|
96
|
-
expanded_key_map = input.keys.inject({}) do |memo, key|
|
97
|
-
memo.merge(key => context.expand_iri(key, vocab: true, as_string: true, base: base))
|
107
|
+
# Revert any previously type-scoped term definitions, unless this is from a map, a value object or a subject reference
|
108
|
+
# FIXME
|
109
|
+
if input.is_a?(Hash) && context.previous_context
|
110
|
+
expanded_key_map = input.keys.inject({}) do |memo, key|
|
111
|
+
memo.merge(key => context.expand_iri(key, vocab: true, as_string: true, base: base))
|
112
|
+
end
|
113
|
+
revert_context = !from_map &&
|
114
|
+
!expanded_key_map.value?('@value') &&
|
115
|
+
expanded_key_map.values != ['@id']
|
116
|
+
context = context.previous_context if revert_context
|
98
117
|
end
|
99
|
-
revert_context = !from_map &&
|
100
|
-
!expanded_key_map.values.include?('@value') &&
|
101
|
-
!(expanded_key_map.values == ['@id'])
|
102
|
-
context = context.previous_context if revert_context
|
103
|
-
end
|
104
118
|
|
105
|
-
|
106
|
-
|
107
|
-
|
119
|
+
# Apply property-scoped context after reverting term-scoped context
|
120
|
+
context = context.parse(property_scoped_context, base: base, override_protected: true) unless
|
121
|
+
property_scoped_context.nil?
|
108
122
|
|
109
|
-
|
110
|
-
|
111
|
-
input = context.expand_value(active_property, input, base: base)
|
112
|
-
end
|
123
|
+
# Otherwise, unless the value is a number, expand the value according to the Value Expansion rules, passing active property.
|
124
|
+
input = context.expand_value(active_property, input, base: base) unless input.is_a?(Hash)
|
113
125
|
|
114
|
-
|
115
|
-
|
116
|
-
|
126
|
+
# Output any type provided from a type map
|
127
|
+
provisional_statements << RDF::Statement(node_id, RDF.type, extra_type) if
|
128
|
+
extra_type
|
117
129
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
130
|
+
# Add statement, either provisionally, or just emit
|
131
|
+
add_statement = proc do |st|
|
132
|
+
if have_id || st.to_quad.none?(node_id)
|
133
|
+
yield(st)
|
134
|
+
else
|
135
|
+
provisional_statements << st
|
136
|
+
end
|
124
137
|
end
|
125
|
-
end
|
126
138
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
"value of @type must be a string: #{term.inspect}" if !term.is_a?(String)
|
146
|
-
term_context = type_scoped_context.term_definitions[term].context if type_scoped_context.term_definitions[term]
|
147
|
-
context = context.parse(term_context, base: base, propagate: false) unless term_context.nil?
|
148
|
-
type = type_scoped_context.expand_iri(term,
|
149
|
-
base: base,
|
150
|
-
documentRelative: true,
|
151
|
-
vocab: true)
|
152
|
-
|
153
|
-
# Early terminate for @json
|
154
|
-
type = RDF.JSON if type == '@json'
|
155
|
-
# Add a provisional statement
|
156
|
-
provisional_statements << RDF::Statement(node_id, RDF.type, type)
|
157
|
-
end
|
158
|
-
state = :await_type
|
159
|
-
when '@id'
|
160
|
-
raise JsonLdError::InvalidSetOrListObject,
|
161
|
-
"found #{key} in state #{state}" if is_list_or_set
|
162
|
-
raise JsonLdError::CollidingKeywords,
|
163
|
-
"found #{key} in state #{state}" unless %i(await_context await_type await_id).include?(state)
|
139
|
+
# Input is an object (Hash), parse keys in order
|
140
|
+
state = :await_context
|
141
|
+
input.each do |key, value|
|
142
|
+
expanded_key = context.expand_iri(key, base: base, vocab: true)
|
143
|
+
case expanded_key
|
144
|
+
when '@context'
|
145
|
+
unless state == :await_context
|
146
|
+
raise JsonLdError::InvalidStreamingKeyOrder,
|
147
|
+
"found #{key} in state #{state}"
|
148
|
+
end
|
149
|
+
context = context.parse(value, base: base)
|
150
|
+
state = :await_type
|
151
|
+
when '@type'
|
152
|
+
# Set the type-scoped context to the context on input, for use later
|
153
|
+
unless %i[await_context await_type].include?(state)
|
154
|
+
raise JsonLdError::InvalidStreamingKeyOrder,
|
155
|
+
"found #{key} in state #{state}"
|
156
|
+
end
|
164
157
|
|
165
|
-
|
166
|
-
|
167
|
-
|
158
|
+
type_scoped_context = context
|
159
|
+
as_array(value).sort.each do |term|
|
160
|
+
unless term.is_a?(String)
|
161
|
+
raise JsonLdError::InvalidTypeValue,
|
162
|
+
"value of @type must be a string: #{term.inspect}"
|
163
|
+
end
|
164
|
+
if type_scoped_context.term_definitions[term]
|
165
|
+
term_context = type_scoped_context.term_definitions[term].context
|
166
|
+
end
|
167
|
+
context = context.parse(term_context, base: base, propagate: false) unless term_context.nil?
|
168
|
+
type = type_scoped_context.expand_iri(term,
|
169
|
+
base: base,
|
170
|
+
documentRelative: true,
|
171
|
+
vocab: true)
|
172
|
+
|
173
|
+
# Early terminate for @json
|
174
|
+
type = RDF.JSON if type == '@json'
|
175
|
+
# Add a provisional statement
|
176
|
+
provisional_statements << RDF::Statement(node_id, RDF.type, type)
|
177
|
+
end
|
178
|
+
state = :await_type
|
179
|
+
when '@id'
|
180
|
+
if is_list_or_set
|
181
|
+
raise JsonLdError::InvalidSetOrListObject,
|
182
|
+
"found #{key} in state #{state}"
|
183
|
+
end
|
184
|
+
unless %i[await_context await_type await_id].include?(state)
|
185
|
+
raise JsonLdError::CollidingKeywords,
|
186
|
+
"found #{key} in state #{state}"
|
187
|
+
end
|
188
|
+
|
189
|
+
# Set our actual id, and use for replacing any provisional statements using our existing node_id, which is provisional
|
190
|
+
unless value.is_a?(String)
|
191
|
+
raise JsonLdError::InvalidIdValue,
|
192
|
+
"value of @id must be a string: #{value.inspect}"
|
193
|
+
end
|
168
194
|
node_reference = input.keys.length == 1
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
195
|
+
expanded_id = context.expand_iri(value, base: base, documentRelative: true)
|
196
|
+
next if expanded_id.nil?
|
197
|
+
|
198
|
+
new_node_id = as_resource(expanded_id)
|
199
|
+
# Replace and emit any statements including our provisional id with the newly established node (or graph) id
|
200
|
+
provisional_statements.each do |st|
|
201
|
+
st.subject = new_node_id if st.subject == node_id
|
202
|
+
st.object = new_node_id if st.object == node_id
|
203
|
+
st.graph_name = new_node_id if st.graph_name == node_id
|
204
|
+
yield(st)
|
205
|
+
end
|
179
206
|
|
180
|
-
|
181
|
-
|
207
|
+
provisional_statements.clear
|
208
|
+
have_id = true
|
209
|
+
node_id = new_node_id
|
182
210
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
#
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
211
|
+
# if there's a subject & predicate, emit that statement now
|
212
|
+
if subject && predicate
|
213
|
+
st = RDF::Statement(subject, predicate, node_id)
|
214
|
+
yield(st)
|
215
|
+
end
|
216
|
+
state = :properties
|
217
|
+
|
218
|
+
when '@direction'
|
219
|
+
if state == :properties
|
220
|
+
raise JsonLdError::InvalidStreamingKeyOrder,
|
221
|
+
"found @direction in state #{state}"
|
222
|
+
end
|
223
|
+
value_object['@direction'] = value
|
224
|
+
state = :await_id
|
225
|
+
when '@graph'
|
226
|
+
# If `@graph` is at the top level (no `subject`) and value contains no keys other than `@graph` and `@context`, add triples to the default graph
|
227
|
+
# Process all graph statements
|
228
|
+
parse_object(value, nil, context) do |st|
|
229
|
+
# If `@graph` is at the top level (`graph_is_named` is `false`) and input contains no keys other than `@graph` and `@context`, add triples to the default graph
|
230
|
+
relevant_keys = input.keys - ['@context', key]
|
231
|
+
st.graph_name = node_id unless !graph_is_named && relevant_keys.empty?
|
232
|
+
if st.graph_name && !st.graph_name.valid?
|
233
|
+
warn "skipping graph statement within invalid graph name: #{st.inspect}"
|
234
|
+
else
|
235
|
+
add_statement.call(st)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
state = :await_id unless state == :properties
|
239
|
+
when '@included'
|
240
|
+
# Expanded values must be node objects
|
241
|
+
have_statements = false
|
242
|
+
parse_object(value, active_property, context) do |st|
|
243
|
+
have_statements ||= st.subject?
|
244
|
+
yield(st)
|
245
|
+
end
|
246
|
+
unless have_statements
|
247
|
+
raise JsonLdError::InvalidIncludedValue,
|
248
|
+
"values of @included must expand to node objects"
|
249
|
+
end
|
250
|
+
|
251
|
+
state = :await_id unless state == :properties
|
252
|
+
when '@index'
|
253
|
+
state = :await_id unless state == :properties
|
254
|
+
unless value.is_a?(String)
|
255
|
+
raise JsonLdError::InvalidIndexValue,
|
256
|
+
"Value of @index is not a string: #{value.inspect}"
|
257
|
+
end
|
258
|
+
when '@language'
|
259
|
+
if state == :properties
|
260
|
+
raise JsonLdError::InvalidStreamingKeyOrder,
|
261
|
+
"found @language in state #{state}"
|
262
|
+
end
|
263
|
+
unless value.is_a?(String)
|
264
|
+
raise JsonLdError::InvalidLanguageTaggedString,
|
265
|
+
"@language value must be a string: #{value.inspect}"
|
266
|
+
end
|
267
|
+
unless /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/.match?(value)
|
268
|
+
warn "@language must be valid BCP47: #{value.inspect}"
|
269
|
+
return
|
270
|
+
end
|
271
|
+
language = value
|
272
|
+
state = :await_id
|
273
|
+
when '@list'
|
274
|
+
unless %i[await_context await_type await_id].include?(state)
|
275
|
+
raise JsonLdError::InvalidSetOrListObject,
|
276
|
+
"found #{key} in state #{state}"
|
277
|
+
end
|
278
|
+
is_list_or_set = true
|
279
|
+
node_id = parse_list(value, active_property, context, &block) if subject
|
280
|
+
state = :properties
|
281
|
+
when '@nest'
|
282
|
+
if context.term_definitions[active_property]
|
283
|
+
nest_context = context.term_definitions[active_property].context
|
284
|
+
end
|
285
|
+
nest_context = if nest_context.nil?
|
286
|
+
context
|
204
287
|
else
|
205
|
-
|
288
|
+
context.parse(nest_context, base: base, override_protected: true)
|
206
289
|
end
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
have_statements = false
|
212
|
-
parse_object(value, active_property, context) do |st|
|
213
|
-
have_statements ||= st.subject?
|
214
|
-
block.call(st)
|
215
|
-
end
|
216
|
-
raise JsonLdError::InvalidIncludedValue, "values of @included must expand to node objects" unless have_statements
|
217
|
-
state = :await_id unless state == :properties
|
218
|
-
when '@index'
|
219
|
-
state = :await_id unless state == :properties
|
220
|
-
raise JsonLdError::InvalidIndexValue,
|
221
|
-
"Value of @index is not a string: #{value.inspect}" unless value.is_a?(String)
|
222
|
-
when '@language'
|
223
|
-
raise JsonLdError::InvalidStreamingKeyOrder,
|
224
|
-
"found @language in state #{state}" if state == :properties
|
225
|
-
raise JsonLdError::InvalidLanguageTaggedString,
|
226
|
-
"@language value must be a string: #{value.inspect}" if !value.is_a?(String)
|
227
|
-
if value !~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/
|
228
|
-
warn "@language must be valid BCP47: #{value.inspect}"
|
229
|
-
return
|
230
|
-
end
|
231
|
-
language = value
|
232
|
-
state = :await_id
|
233
|
-
when '@list'
|
234
|
-
raise JsonLdError::InvalidSetOrListObject,
|
235
|
-
"found #{key} in state #{state}" if
|
236
|
-
!%i(await_context await_type await_id).include?(state)
|
237
|
-
is_list_or_set = true
|
238
|
-
if subject
|
239
|
-
node_id = parse_list(value, active_property, context, &block)
|
240
|
-
end
|
241
|
-
state = :properties
|
242
|
-
when '@nest'
|
243
|
-
nest_context = context.term_definitions[active_property].context if context.term_definitions[active_property]
|
244
|
-
nest_context = if nest_context.nil?
|
245
|
-
context
|
246
|
-
else
|
247
|
-
context.parse(nest_context, base: base, override_protected: true)
|
248
|
-
end
|
249
|
-
as_array(value).each do |v|
|
250
|
-
raise JsonLdError::InvalidNestValue, v.inspect unless
|
251
|
-
v.is_a?(Hash) && v.keys.none? {|k| nest_context.expand_iri(k, vocab: true, base: base) == '@value'}
|
290
|
+
as_array(value).each do |v|
|
291
|
+
raise JsonLdError::InvalidNestValue, v.inspect unless
|
292
|
+
v.is_a?(Hash) && v.keys.none? { |k| nest_context.expand_iri(k, vocab: true, base: base) == '@value' }
|
293
|
+
|
252
294
|
parse_object(v, active_property, nest_context, node_id: node_id) do |st|
|
253
295
|
add_statement.call(st)
|
254
296
|
end
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
297
|
+
end
|
298
|
+
state = :await_id unless state == :properties
|
299
|
+
when '@reverse'
|
300
|
+
as_array(value).each do |item|
|
301
|
+
item = context.expand_value(active_property, item, base: base) unless item.is_a?(Hash)
|
302
|
+
raise JsonLdError::InvalidReverseValue, item.inspect if value?(item)
|
303
|
+
raise JsonLdError::InvalidReversePropertyMap, item.inspect if node_reference?(item)
|
304
|
+
raise JsonLdError::InvalidReversePropertyValue, item.inspect if list?(item)
|
305
|
+
|
306
|
+
has_own_subject = false
|
307
|
+
parse_object(item, active_property, context, node_id: node_id, predicate: predicate) do |st|
|
308
|
+
if st.subject == node_id
|
309
|
+
raise JsonLdError::InvalidReversePropertyValue, item.inspect unless st.object.resource?
|
310
|
+
|
311
|
+
# Invert sense of statements
|
312
|
+
st = RDF::Statement(st.object, st.predicate, st.subject)
|
313
|
+
has_own_subject = true
|
314
|
+
end
|
315
|
+
add_statement.call(st)
|
270
316
|
end
|
271
|
-
|
317
|
+
|
318
|
+
# If the reversed node does not make any claims on this subject, it's an error
|
319
|
+
raise JsonLdError::InvalidReversePropertyValue, item.inspect unless has_own_subject
|
320
|
+
end
|
321
|
+
state = :await_id unless state == :properties
|
322
|
+
when '@set'
|
323
|
+
unless %i[await_context await_type await_id].include?(state)
|
324
|
+
raise JsonLdError::InvalidSetOrListObject,
|
325
|
+
"found #{key} in state #{state}"
|
326
|
+
end
|
327
|
+
is_list_or_set = true
|
328
|
+
value = as_array(value).compact
|
329
|
+
parse_object(value, active_property, context, subject: subject, predicate: predicate, &block)
|
330
|
+
node_id = nil
|
331
|
+
state = :properties
|
332
|
+
when '@value'
|
333
|
+
if state == :properties
|
334
|
+
raise JsonLdError::InvalidStreamingKeyOrder,
|
335
|
+
"found @value in state #{state}"
|
272
336
|
end
|
337
|
+
value_object['@value'] = value
|
338
|
+
state = :await_id
|
339
|
+
else
|
340
|
+
state = :await_id unless state == :properties
|
341
|
+
# Skip keys that don't expand to a keyword or absolute IRI
|
342
|
+
next if expanded_key.is_a?(RDF::URI) && !expanded_key.absolute?
|
273
343
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
state = :await_id unless state == :properties
|
278
|
-
when '@set'
|
279
|
-
raise JsonLdError::InvalidSetOrListObject,
|
280
|
-
"found #{key} in state #{state}" if
|
281
|
-
!%i(await_context await_type await_id).include?(state)
|
282
|
-
is_list_or_set = true
|
283
|
-
value = as_array(value).compact
|
284
|
-
parse_object(value, active_property, context, subject: subject, predicate: predicate, &block)
|
285
|
-
node_id = nil
|
286
|
-
state = :properties
|
287
|
-
when '@value'
|
288
|
-
raise JsonLdError::InvalidStreamingKeyOrder,
|
289
|
-
"found @value in state #{state}" if state == :properties
|
290
|
-
value_object['@value'] = value
|
291
|
-
state = :await_id
|
292
|
-
else
|
293
|
-
state = :await_id unless state == :properties
|
294
|
-
# Skip keys that don't expand to a keyword or absolute IRI
|
295
|
-
next if expanded_key.is_a?(RDF::URI) && !expanded_key.absolute?
|
296
|
-
parse_property(value, key, context, node_id, expanded_key) do |st|
|
297
|
-
add_statement.call(st)
|
344
|
+
parse_property(value, key, context, node_id, expanded_key) do |st|
|
345
|
+
add_statement.call(st)
|
346
|
+
end
|
298
347
|
end
|
299
348
|
end
|
300
|
-
end
|
301
349
|
|
302
|
-
|
303
|
-
|
304
|
-
"value object has unknown key: @id" if
|
305
|
-
!value_object.empty? && (have_id || is_list_or_set)
|
306
|
-
|
307
|
-
# Can't have both @id and either @list or @set
|
308
|
-
raise JsonLdError::InvalidSetOrListObject,
|
309
|
-
"found @id with @list or @set" if
|
310
|
-
have_id && is_list_or_set
|
311
|
-
|
312
|
-
type_statements = provisional_statements.select {|ps| ps.predicate == RDF.type && ps.graph_name.nil?}
|
313
|
-
value_object['@language'] = (@options[:lowercaseLanguage] ? language.downcase : language) if language
|
314
|
-
if !value_object.empty? &&
|
315
|
-
(!value_object['@value'].nil? ||
|
316
|
-
(type_statements.first || RDF::Statement.new).object == RDF.JSON)
|
317
|
-
|
318
|
-
# There can be only one value of @type
|
319
|
-
case type_statements.length
|
320
|
-
when 0 then #skip
|
321
|
-
when 1
|
322
|
-
raise JsonLdError::InvalidTypedValue,
|
323
|
-
"value of @type must be an IRI or '@json': #{type_statements.first.object.inspect}" unless
|
324
|
-
type_statements.first.object.valid?
|
325
|
-
value_object['@type'] = type_statements.first.object
|
326
|
-
else
|
350
|
+
# Value object with @id
|
351
|
+
if !value_object.empty? && (have_id || is_list_or_set)
|
327
352
|
raise JsonLdError::InvalidValueObject,
|
328
|
-
|
353
|
+
"value object has unknown key: @id"
|
354
|
+
end
|
355
|
+
|
356
|
+
# Can't have both @id and either @list or @set
|
357
|
+
if have_id && is_list_or_set
|
358
|
+
raise JsonLdError::InvalidSetOrListObject,
|
359
|
+
"found @id with @list or @set"
|
329
360
|
end
|
330
361
|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
raise JsonLdError::InvalidTypedValue,
|
347
|
-
"value of @type must be an IRI or '@json': #{value_object['@type'].inspect}" unless
|
348
|
-
value_object['@type'].is_a?(RDF::URI)
|
349
|
-
elsif value_object['@type'] != RDF.JSON
|
350
|
-
case value_object['@value']
|
351
|
-
when String, TrueClass, FalseClass, Numeric then # okay
|
362
|
+
type_statements = provisional_statements.select { |ps| ps.predicate == RDF.type && ps.graph_name.nil? }
|
363
|
+
value_object['@language'] = (@options[:lowercaseLanguage] ? language.downcase : language) if language
|
364
|
+
if !value_object.empty? &&
|
365
|
+
(!value_object['@value'].nil? ||
|
366
|
+
(type_statements.first || RDF::Statement.new).object == RDF.JSON)
|
367
|
+
|
368
|
+
# There can be only one value of @type
|
369
|
+
case type_statements.length
|
370
|
+
when 0 # skip
|
371
|
+
when 1
|
372
|
+
unless type_statements.first.object.valid?
|
373
|
+
raise JsonLdError::InvalidTypedValue,
|
374
|
+
"value of @type must be an IRI or '@json': #{type_statements.first.object.inspect}"
|
375
|
+
end
|
376
|
+
value_object['@type'] = type_statements.first.object
|
352
377
|
else
|
353
|
-
raise JsonLdError::
|
354
|
-
|
378
|
+
raise JsonLdError::InvalidValueObject,
|
379
|
+
"value object must not have more than one type"
|
355
380
|
end
|
356
|
-
end
|
357
|
-
literal = item_to_rdf(value_object, &block)
|
358
|
-
st = RDF::Statement(subject, predicate, literal)
|
359
|
-
block.call(st)
|
360
|
-
elsif !provisional_statements.empty?
|
361
|
-
# Emit all provisional statements, as no @id was ever found
|
362
|
-
provisional_statements.each {|st| block.call(st)}
|
363
|
-
end
|
364
381
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
382
|
+
# Check for extra keys
|
383
|
+
unless (value_object.keys - Expand::KEYS_VALUE_LANGUAGE_TYPE_INDEX_DIRECTION).empty?
|
384
|
+
raise JsonLdError::InvalidValueObject,
|
385
|
+
"value object has unknown keys: #{value_object.inspect}"
|
386
|
+
end
|
370
387
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
if lang !~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/ && expanded_lang != '@none'
|
377
|
-
warn "@language must be valid BCP47: #{lang.inspect}"
|
388
|
+
# @type is inconsistent with either @language or @direction
|
389
|
+
if value_object.key?('@type') && !(value_object.keys & %w[@language @direction]).empty?
|
390
|
+
raise JsonLdError::InvalidValueObject,
|
391
|
+
"value object must not include @type with either " \
|
392
|
+
"@language or @direction: #{value_object.inspect}"
|
378
393
|
end
|
379
394
|
|
380
|
-
|
381
|
-
raise JsonLdError::
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
395
|
+
if value_object.key?('@language') && !value_object['@value'].is_a?(String)
|
396
|
+
raise JsonLdError::InvalidLanguageTaggedValue,
|
397
|
+
"with @language @value must be a string: #{value_object.inspect}"
|
398
|
+
elsif value_object['@type'] && value_object['@type'] != RDF.JSON
|
399
|
+
unless value_object['@type'].is_a?(RDF::URI)
|
400
|
+
raise JsonLdError::InvalidTypedValue,
|
401
|
+
"value of @type must be an IRI or '@json': #{value_object['@type'].inspect}"
|
402
|
+
end
|
403
|
+
elsif value_object['@type'] != RDF.JSON
|
404
|
+
case value_object['@value']
|
405
|
+
when String, TrueClass, FalseClass, Numeric # okay
|
406
|
+
else
|
407
|
+
raise JsonLdError::InvalidValueObjectValue,
|
408
|
+
"@value is: #{value_object['@value'].inspect}"
|
409
|
+
end
|
387
410
|
end
|
411
|
+
literal = item_to_rdf(value_object, &block)
|
412
|
+
st = RDF::Statement(subject, predicate, literal)
|
413
|
+
yield(st)
|
414
|
+
elsif !provisional_statements.empty?
|
415
|
+
# Emit all provisional statements, as no @id was ever found
|
416
|
+
provisional_statements.each(&block)
|
388
417
|
end
|
389
|
-
|
390
|
-
#
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
418
|
+
|
419
|
+
# Use implicit subject to generate the relationship
|
420
|
+
return unless value_object.empty? && subject && predicate && !have_id && !node_reference
|
421
|
+
|
422
|
+
yield(RDF::Statement(subject, predicate, node_id))
|
423
|
+
end
|
424
|
+
|
425
|
+
def parse_property(input, active_property, context, subject, predicate, &block)
|
426
|
+
container = context.container(active_property)
|
427
|
+
if container.include?('@language') && input.is_a?(Hash)
|
428
|
+
input.each do |lang, lang_value|
|
429
|
+
expanded_lang = context.expand_iri(lang, vocab: true)
|
430
|
+
if lang !~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/ && expanded_lang != '@none'
|
431
|
+
warn "@language must be valid BCP47: #{lang.inspect}"
|
432
|
+
end
|
433
|
+
|
434
|
+
as_array(lang_value).each do |item|
|
435
|
+
unless item.nil? || item.is_a?(String)
|
436
|
+
raise JsonLdError::InvalidLanguageMapValue,
|
437
|
+
"Expected #{item.inspect} to be a string"
|
438
|
+
end
|
439
|
+
lang_obj = { '@value' => item }
|
440
|
+
lang_obj['@language'] = lang unless expanded_lang == '@none'
|
441
|
+
lang_obj['@direction'] = context.direction(lang) if context.direction(lang)
|
442
|
+
parse_object(lang_obj, active_property, context, subject: subject, predicate: predicate, &block)
|
443
|
+
end
|
444
|
+
end
|
445
|
+
elsif container.include?('@list')
|
446
|
+
# Handle case where value is a list object
|
447
|
+
if input.is_a?(Hash) &&
|
448
|
+
input.keys.map do |k|
|
449
|
+
context.expand_iri(k, vocab: true, as_string: true, base: base)
|
450
|
+
end.include?('@list')
|
451
|
+
parse_object(input, active_property, context,
|
452
|
+
subject: subject, predicate: predicate, &block)
|
409
453
|
else
|
410
|
-
|
454
|
+
list = parse_list(input, active_property, context, &block)
|
455
|
+
yield(RDF::Statement(subject, predicate, list))
|
411
456
|
end
|
412
|
-
|
413
|
-
context
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
457
|
+
elsif container.intersect?(JSON::LD::Expand::CONTAINER_INDEX_ID_TYPE) && input.is_a?(Hash)
|
458
|
+
# Get appropriate context for this container
|
459
|
+
container_context = if container.include?('@type') && context.previous_context
|
460
|
+
context.previous_context
|
461
|
+
elsif container.include?('@id') && context.term_definitions[active_property]
|
462
|
+
id_context = context.term_definitions[active_property].context if context.term_definitions[active_property]
|
463
|
+
if id_context.nil?
|
464
|
+
context
|
465
|
+
else
|
466
|
+
context.parse(id_context, base: base, propagate: false)
|
467
|
+
end
|
468
|
+
else
|
469
|
+
context
|
422
470
|
end
|
423
|
-
map_context ||= container_context
|
424
|
-
|
425
|
-
expanded_k = container_context.expand_iri(k, vocab: true, as_string: true, base: base)
|
426
|
-
index_key = context.term_definitions[active_property].index || '@index'
|
427
|
-
|
428
|
-
case
|
429
|
-
when container.include?('@index') && container.include?('@graph')
|
430
|
-
# Index is ignored
|
431
|
-
as_array(v).each do |item|
|
432
|
-
# Each value is in a separate graph
|
433
|
-
graph_name = RDF::Node.new(namer.get_sym)
|
434
|
-
parse_object(item, active_property, context) do |st|
|
435
|
-
st.graph_name ||= graph_name
|
436
|
-
block.call(st)
|
437
|
-
end
|
438
|
-
block.call(RDF::Statement(subject, predicate, graph_name))
|
439
471
|
|
440
|
-
|
441
|
-
|
472
|
+
input.each do |k, v|
|
473
|
+
# If container mapping in the active context includes @type, and k is a term in the active context having a local context, use that context when expanding values
|
474
|
+
map_context = container_context.term_definitions[k].context if
|
475
|
+
container.include?('@type') && container_context.term_definitions[k]
|
476
|
+
map_context = container_context.parse(map_context, base: base, propagate: false) unless map_context.nil?
|
477
|
+
map_context ||= container_context
|
478
|
+
|
479
|
+
expanded_k = container_context.expand_iri(k, vocab: true, as_string: true, base: base)
|
480
|
+
index_key = context.term_definitions[active_property].index || '@index'
|
481
|
+
|
482
|
+
if container.include?('@index') && container.include?('@graph')
|
483
|
+
# Index is ignored
|
484
|
+
as_array(v).each do |item|
|
485
|
+
# Each value is in a separate graph
|
486
|
+
graph_name = RDF::Node.new(namer.get_sym)
|
487
|
+
parse_object(item, active_property, context) do |st|
|
488
|
+
st.graph_name ||= graph_name
|
489
|
+
yield(st)
|
490
|
+
end
|
491
|
+
yield(RDF::Statement(subject, predicate, graph_name))
|
492
|
+
|
493
|
+
# Add a property index, if appropriate
|
494
|
+
next if index_key == '@index'
|
495
|
+
|
442
496
|
# Expand key based on term
|
443
|
-
expanded_k = k == '@none'
|
444
|
-
'@none'
|
497
|
+
expanded_k = if k == '@none'
|
498
|
+
'@none'
|
499
|
+
else
|
445
500
|
container_context.expand_value(index_key, k, base: base)
|
501
|
+
end
|
446
502
|
|
447
503
|
# Add the index property as a property of the graph name
|
448
504
|
index_property = container_context.expand_iri(index_key, vocab: true, base: base)
|
449
|
-
|
450
|
-
|
451
|
-
|
505
|
+
unless expanded_k == '@none'
|
506
|
+
emit_object(expanded_k, index_key, map_context, graph_name,
|
507
|
+
index_property, from_map: true, &block)
|
508
|
+
end
|
452
509
|
end
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
510
|
+
elsif container.include?('@index')
|
511
|
+
if index_key == '@index'
|
512
|
+
# Index is ignored
|
513
|
+
emit_object(v, active_property, map_context, subject, predicate, from_map: true, &block)
|
514
|
+
else
|
515
|
+
# Expand key based on term
|
516
|
+
expanded_k = if k == '@none'
|
517
|
+
'@none'
|
518
|
+
else
|
519
|
+
container_context.expand_value(index_key, k, base: base)
|
520
|
+
end
|
521
|
+
|
522
|
+
index_property = container_context.expand_iri(index_key, vocab: true, as_string: true, base: base)
|
523
|
+
|
524
|
+
# index_key is a property
|
525
|
+
as_array(v).each do |item|
|
526
|
+
item = container_context.expand_value(active_property, item, base: base) if item.is_a?(String)
|
527
|
+
if value?(item)
|
528
|
+
raise JsonLdError::InvalidValueObject,
|
529
|
+
"Attempt to add illegal key to value object: #{index_key}"
|
530
|
+
end
|
531
|
+
# add expanded_k as value of index_property in item
|
532
|
+
item[index_property] = [expanded_k].concat(Array(item[index_property])) unless expanded_k == '@none'
|
533
|
+
emit_object(item, active_property, map_context, subject, predicate, from_map: true, &block)
|
534
|
+
end
|
535
|
+
end
|
536
|
+
elsif container.include?('@id') && container.include?('@graph')
|
537
|
+
graph_name = if expanded_k == '@none'
|
538
|
+
RDF::Node.new(namer.get_sym)
|
539
|
+
else
|
540
|
+
container_context.expand_iri(k, documentRelative: true, base: base)
|
474
541
|
end
|
542
|
+
parse_object(v, active_property, context) do |st|
|
543
|
+
st.graph_name ||= graph_name
|
544
|
+
yield(st)
|
545
|
+
end
|
546
|
+
yield(RDF::Statement(subject, predicate, graph_name))
|
547
|
+
elsif container.include?('@id')
|
548
|
+
expanded_k = container_context.expand_iri(k, documentRelative: true, base: base)
|
549
|
+
# pass our id
|
550
|
+
emit_object(v, active_property, map_context, subject, predicate,
|
551
|
+
node_id: (expanded_k unless expanded_k == '@none'),
|
552
|
+
from_map: true,
|
553
|
+
&block)
|
554
|
+
elsif container.include?('@type')
|
555
|
+
emit_object(v, active_property, map_context, subject, predicate,
|
556
|
+
from_map: true,
|
557
|
+
extra_type: as_resource(expanded_k),
|
558
|
+
&block)
|
475
559
|
end
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
560
|
+
end
|
561
|
+
elsif container.include?('@graph')
|
562
|
+
# Index is ignored
|
563
|
+
as_array(input).each do |v|
|
564
|
+
# Each value is in a separate graph
|
565
|
+
graph_name = RDF::Node.new(namer.get_sym)
|
480
566
|
parse_object(v, active_property, context) do |st|
|
481
567
|
st.graph_name ||= graph_name
|
482
|
-
|
483
|
-
end
|
484
|
-
|
485
|
-
when container.include?('@id')
|
486
|
-
expanded_k = container_context.expand_iri(k, documentRelative: true, base: base)
|
487
|
-
# pass our id
|
488
|
-
emit_object(v, active_property, map_context, subject, predicate,
|
489
|
-
node_id: (expanded_k unless expanded_k == '@none'),
|
490
|
-
from_map: true,
|
491
|
-
&block)
|
492
|
-
when container.include?('@type')
|
493
|
-
emit_object(v, active_property, map_context, subject, predicate,
|
494
|
-
from_map: true,
|
495
|
-
extra_type: as_resource(expanded_k),
|
496
|
-
&block)
|
497
|
-
end
|
498
|
-
end
|
499
|
-
elsif container.include?('@graph')
|
500
|
-
# Index is ignored
|
501
|
-
as_array(input).each do |v|
|
502
|
-
# Each value is in a separate graph
|
503
|
-
graph_name = RDF::Node.new(namer.get_sym)
|
504
|
-
parse_object(v, active_property, context) do |st|
|
505
|
-
st.graph_name ||= graph_name
|
506
|
-
block.call(st)
|
568
|
+
yield(st)
|
569
|
+
end
|
570
|
+
yield(RDF::Statement(subject, predicate, graph_name))
|
507
571
|
end
|
508
|
-
|
572
|
+
else
|
573
|
+
emit_object(input, active_property, context, subject, predicate, &block)
|
509
574
|
end
|
510
|
-
else
|
511
|
-
emit_object(input, active_property, context, subject, predicate, &block)
|
512
575
|
end
|
513
|
-
end
|
514
576
|
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
has_own_subject = false
|
526
|
-
parse_object(item, active_property, context, subject: subject, predicate: predicate, **options) do |st|
|
527
|
-
if st.subject == subject
|
528
|
-
raise JsonLdError::InvalidReversePropertyValue, item.inspect if !st.object.resource?
|
529
|
-
# Invert sense of statements
|
530
|
-
st = RDF::Statement(st.object, st.predicate, st.subject)
|
531
|
-
has_own_subject = true
|
532
|
-
end
|
533
|
-
block.call(st)
|
534
|
-
end
|
577
|
+
# Wrapps parse_object to handle JSON literals and reversed properties
|
578
|
+
def emit_object(input, active_property, context, subject, predicate, **options, &block)
|
579
|
+
if context.coerce(active_property) == '@json'
|
580
|
+
parse_object(context.expand_value(active_property, input), active_property, context,
|
581
|
+
subject: subject, predicate: predicate, **options, &block)
|
582
|
+
elsif context.reverse?(active_property)
|
583
|
+
as_array(input).each do |item|
|
584
|
+
item = context.expand_value(active_property, item, base: base) unless item.is_a?(Hash)
|
585
|
+
raise JsonLdError::InvalidReverseValue, item.inspect if value?(item)
|
586
|
+
raise JsonLdError::InvalidReversePropertyValue, item.inspect if list?(item)
|
535
587
|
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
588
|
+
has_own_subject = false
|
589
|
+
parse_object(item, active_property, context, subject: subject, predicate: predicate, **options) do |st|
|
590
|
+
if st.subject == subject
|
591
|
+
raise JsonLdError::InvalidReversePropertyValue, item.inspect unless st.object.resource?
|
592
|
+
|
593
|
+
# Invert sense of statements
|
594
|
+
st = RDF::Statement(st.object, st.predicate, st.subject)
|
595
|
+
has_own_subject = true
|
596
|
+
end
|
597
|
+
yield(st)
|
598
|
+
end
|
599
|
+
|
600
|
+
# If the reversed node does not make any claims on this subject, it's an error
|
601
|
+
unless has_own_subject
|
602
|
+
raise JsonLdError::InvalidReversePropertyValue,
|
603
|
+
"@reverse value must be a node: #{value.inspect}"
|
604
|
+
end
|
605
|
+
end
|
606
|
+
else
|
607
|
+
as_array(input).flatten.each do |item|
|
608
|
+
# emit property/value
|
609
|
+
parse_object(item, active_property, context,
|
610
|
+
subject: subject, predicate: predicate, **options, &block)
|
611
|
+
end
|
545
612
|
end
|
546
613
|
end
|
547
|
-
end
|
548
614
|
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
615
|
+
# Process input as an ordered list
|
616
|
+
# @return [RDF::Resource] the list head
|
617
|
+
def parse_list(input, active_property, context, &block)
|
618
|
+
# Transform all entries into their values
|
619
|
+
# this allows us to eliminate those that don't create any statements
|
620
|
+
fake_subject = RDF::Node.new
|
621
|
+
values = as_array(input).map do |entry|
|
622
|
+
if entry.is_a?(Array)
|
623
|
+
# recursive list
|
624
|
+
entry_value = parse_list(entry, active_property, context, &block)
|
625
|
+
else
|
626
|
+
entry_value = nil
|
627
|
+
parse_object(entry, active_property, context, subject: fake_subject, predicate: RDF.first) do |st|
|
628
|
+
if st.subject == fake_subject
|
629
|
+
entry_value = st.object
|
630
|
+
else
|
631
|
+
yield(st)
|
632
|
+
end
|
566
633
|
end
|
634
|
+
entry_value
|
567
635
|
end
|
568
|
-
|
569
|
-
|
570
|
-
end.compact
|
571
|
-
return RDF.nil if values.empty?
|
636
|
+
end.compact
|
637
|
+
return RDF.nil if values.empty?
|
572
638
|
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
639
|
+
# Construct a list from values, and emit list statements, returning the list subject
|
640
|
+
list = RDF::List(*values)
|
641
|
+
list.each_statement(&block)
|
642
|
+
list.subject
|
643
|
+
end
|
577
644
|
end
|
578
645
|
end
|
579
|
-
end
|
646
|
+
end
|