json-ld 0.1.0 → 0.1.2
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.
- data/History.markdown +15 -0
- data/README.markdown +199 -3
- data/VERSION +1 -1
- data/lib/json/ld.rb +44 -4
- data/lib/json/ld/api.rb +220 -224
- data/lib/json/ld/compact.rb +126 -0
- data/lib/json/ld/evaluation_context.rb +428 -204
- data/lib/json/ld/expand.rb +185 -0
- data/lib/json/ld/extensions.rb +34 -7
- data/lib/json/ld/format.rb +2 -17
- data/lib/json/ld/frame.rb +452 -0
- data/lib/json/ld/from_rdf.rb +166 -0
- data/lib/json/ld/reader.rb +7 -231
- data/lib/json/ld/to_rdf.rb +181 -0
- data/lib/json/ld/utils.rb +97 -0
- data/lib/json/ld/writer.rb +33 -471
- metadata +51 -34
- data/lib/json/ld/normalize.rb +0 -120
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'rdf/nquads'
|
2
|
+
|
3
|
+
module JSON::LD
|
4
|
+
module FromTriples
|
5
|
+
include Utils
|
6
|
+
|
7
|
+
##
|
8
|
+
# Generate a JSON-LD array representation from an array of `RDF::Statement`.
|
9
|
+
# Representation is in expanded form
|
10
|
+
#
|
11
|
+
# @param [Array<RDF::Statement>] input
|
12
|
+
# @param [BlankNodeNamer] namer
|
13
|
+
# @return [Array<Hash>] the JSON-LD document in normalized form
|
14
|
+
def from_statements(input, namer)
|
15
|
+
array = []
|
16
|
+
listMap = {}
|
17
|
+
restMap = {}
|
18
|
+
subjectMap = {}
|
19
|
+
bnode_map = {}
|
20
|
+
|
21
|
+
value = nil
|
22
|
+
ec = EvaluationContext.new
|
23
|
+
|
24
|
+
# Create a map for subject to object representation
|
25
|
+
|
26
|
+
# For each triple in input
|
27
|
+
input.each do |statement|
|
28
|
+
debug("statement") { statement.to_nquads.chomp}
|
29
|
+
|
30
|
+
subject = ec.expand_iri(statement.subject).to_s
|
31
|
+
name = ec.expand_iri(statement.context).to_s if statement.context
|
32
|
+
subject = namer.get_name(subject) if subject[0,2] == "_:"
|
33
|
+
name = namer.get_name(name) if name.to_s[0,2] == "_:"
|
34
|
+
|
35
|
+
case statement.predicate
|
36
|
+
when RDF.first
|
37
|
+
# If property is rdf:first,
|
38
|
+
# create a new entry in _listMap_ for _name_ and _subject_ and an array value
|
39
|
+
# containing the object representation and continue to the next statement.
|
40
|
+
object_rep = ec.expand_value(nil, statement.object)
|
41
|
+
object_rep['@id'] = namer.get_name(object_rep['@id']) if blank_node?(object_rep)
|
42
|
+
debug("rdf:first") { "save object #{[object_rep].inspect}"}
|
43
|
+
listMap[name] ||= {}
|
44
|
+
listMap[name][subject] = [object_rep]
|
45
|
+
next
|
46
|
+
when RDF.rest
|
47
|
+
# If property is rdf:rest,
|
48
|
+
# and object is a blank node,
|
49
|
+
# create a new entry in _restMap_ for _name_ and _subject_ and a value being the
|
50
|
+
# result of IRI expansion on the object and continue to the next statement.
|
51
|
+
next unless statement.object.is_a?(RDF::Node)
|
52
|
+
object_rep = ec.expand_iri(statement.object).to_s
|
53
|
+
object_rep = namer.get_name(object_rep) if object_rep[0,2] == '_:'
|
54
|
+
debug("rdf:rest") { "save object #{object_rep.inspect}"}
|
55
|
+
restMap[name] ||= {}
|
56
|
+
restMap[name][subject] = object_rep
|
57
|
+
next
|
58
|
+
end
|
59
|
+
|
60
|
+
# If name is not null
|
61
|
+
if name
|
62
|
+
# If _subjectMap_ does not have an entry for null as name and _name_ as subject
|
63
|
+
subjectMap[nil] ||= {}
|
64
|
+
value = subjectMap[nil][name]
|
65
|
+
unless value
|
66
|
+
# Create a new JSON Object with key/value pair of @id and a string representation
|
67
|
+
# of name and append to array.
|
68
|
+
debug("@id") { "new subject: #{name} for graph"}
|
69
|
+
value = Hash.ordered
|
70
|
+
value['@id'] = name
|
71
|
+
array << (subjectMap[nil][name] = value)
|
72
|
+
else
|
73
|
+
# Otherwise, use that entry as value
|
74
|
+
end
|
75
|
+
|
76
|
+
# If value does not have an entry for @graph, initialize it as a new array
|
77
|
+
a = value['@graph'] ||= []
|
78
|
+
|
79
|
+
# If subjectMap does not have an entry for name and subject
|
80
|
+
subjectMap[name] ||= {}
|
81
|
+
value = subjectMap[name][subject]
|
82
|
+
unless value
|
83
|
+
# Create a new JSON Object with key/value pair of @id and a string representation
|
84
|
+
# of name and append to the the graph array for name and use as value.
|
85
|
+
debug("@id") { "new subject: #{subject} for graph: #{name}"}
|
86
|
+
value = Hash.ordered
|
87
|
+
value['@id'] = subject
|
88
|
+
a << (subjectMap[name][subject] = value)
|
89
|
+
else
|
90
|
+
# Otherwise, use that entry as value
|
91
|
+
end
|
92
|
+
else
|
93
|
+
# Otherwise, if subjectMap does not have an entry for _name_ and _subject_
|
94
|
+
subjectMap[name] ||= {}
|
95
|
+
value = subjectMap[nil][subject]
|
96
|
+
unless value
|
97
|
+
# Create a new JSON Object with key/value pair of @id and a string representation
|
98
|
+
# of subject and append to array.
|
99
|
+
debug("@id") { "new subject: #{subject}"}
|
100
|
+
value = Hash.ordered
|
101
|
+
value['@id'] = subject
|
102
|
+
array << (subjectMap[nil][subject] = value)
|
103
|
+
else
|
104
|
+
# Otherwise, use that entry as value
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# If property is http://www.w3.org/1999/02/22-rdf-syntax-ns#type:
|
109
|
+
if statement.predicate == RDF.type
|
110
|
+
object = ec.expand_iri(statement.object).to_s
|
111
|
+
debug("@type") { object.inspect}
|
112
|
+
# append the string representation of object to the array value for the key @type, creating
|
113
|
+
# an entry if necessary
|
114
|
+
(value['@type'] ||= []) << object
|
115
|
+
elsif statement.object == RDF.nil
|
116
|
+
# Otherwise, if object is http://www.w3.org/1999/02/22-rdf-syntax-ns#nil, let
|
117
|
+
# key be the string representation of predicate. Set the value
|
118
|
+
# for key to an empty @list representation {"@list": []}
|
119
|
+
key = ec.expand_iri(statement.predicate).to_s
|
120
|
+
value[key] = {"@list" => []}
|
121
|
+
else
|
122
|
+
# Otherwise, let key be the string representation of predicate and let object representation
|
123
|
+
# be object represented in expanded form as described in Value Expansion.
|
124
|
+
key = ec.expand_iri(statement.predicate).to_s
|
125
|
+
object = ec.expand_value(key, statement.object)
|
126
|
+
debug("object") {"detected that #{object.inspect} is a blank node"}
|
127
|
+
object['@id'] = object_iri = namer.get_name(object['@id']) if blank_node?(object)
|
128
|
+
|
129
|
+
debug("key/value") { "key: #{key}, :value #{object.inspect}"}
|
130
|
+
|
131
|
+
# Non-normative, save a reference for the bnode to allow for easier list expansion
|
132
|
+
bnode_map[object_iri] = {:obj => value, :key => key} if statement.object.is_a?(RDF::Node)
|
133
|
+
|
134
|
+
# append the object object representation to the array value for key, creating
|
135
|
+
# an entry if necessary
|
136
|
+
(value[key] ||= []) << object
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# For each key/value _prev_, _rest_ entry in _restMap_, append to the _listMap_ value identified
|
141
|
+
# by _prev_ the _listMap_ value identified by _rest_
|
142
|
+
debug("restMap") {restMap.inspect}
|
143
|
+
restMap.each do |gname, map|
|
144
|
+
map.each do |prev, rest|
|
145
|
+
debug("@list") { "Fold #{rest} into #{prev}"}
|
146
|
+
listMap[gname][prev] += listMap[gname][rest]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# For each key/value _node_, _list_, in _listMap_ where _list_ exists as a value of an object in _array_,
|
151
|
+
# replace the object value with _list_
|
152
|
+
debug("listMap") {listMap.inspect}
|
153
|
+
listMap.each do |gname, map|
|
154
|
+
map.each do |node, list|
|
155
|
+
next unless bnode_map.has_key?(node)
|
156
|
+
debug("@list") { "Replace #{bnode_map[node][:obj][bnode_map[node][:key]]} with #{listMap[node]}"}
|
157
|
+
bnode_map[node][:obj][bnode_map[node][:key]] = {"@list" => listMap[gname][node]}
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Return array as the graph representation.
|
162
|
+
debug("fromRdf") {array.to_json(JSON_STATE)}
|
163
|
+
array
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
data/lib/json/ld/reader.rb
CHANGED
@@ -10,10 +10,10 @@ module JSON::LD
|
|
10
10
|
format Format
|
11
11
|
|
12
12
|
##
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
# Override normal symbol generation
|
14
|
+
def self.to_sym
|
15
|
+
:jsonld
|
16
|
+
end
|
17
17
|
|
18
18
|
##
|
19
19
|
# Initializes the RDF/JSON reader instance.
|
@@ -26,6 +26,8 @@ module JSON::LD
|
|
26
26
|
# @yieldreturn [void] ignored
|
27
27
|
# @raise [RDF::ReaderError] if the JSON document cannot be loaded
|
28
28
|
def initialize(input = $stdin, options = {}, &block)
|
29
|
+
options[:base_uri] ||= options[:base] if options.has_key?(:base)
|
30
|
+
options[:base] ||= options[:base_uri] if options.has_key?(:base_uri)
|
29
31
|
super do
|
30
32
|
begin
|
31
33
|
@doc = JSON.load(input)
|
@@ -47,12 +49,7 @@ module JSON::LD
|
|
47
49
|
# @private
|
48
50
|
# @see RDF::Reader#each_statement
|
49
51
|
def each_statement(&block)
|
50
|
-
@
|
51
|
-
|
52
|
-
# initialize the evaluation context with initial context
|
53
|
-
ec = EvaluationContext.new(@options)
|
54
|
-
|
55
|
-
traverse("", @doc, nil, nil, ec)
|
52
|
+
JSON::LD::API.toRDF(@doc, @options[:context], nil, @options, &block)
|
56
53
|
end
|
57
54
|
|
58
55
|
##
|
@@ -63,227 +60,6 @@ module JSON::LD
|
|
63
60
|
block.call(*statement.to_triple)
|
64
61
|
end
|
65
62
|
end
|
66
|
-
|
67
|
-
private
|
68
|
-
##
|
69
|
-
#
|
70
|
-
# @param [String] path
|
71
|
-
# location within JSON hash
|
72
|
-
# @param [Hash, Array, String] element
|
73
|
-
# The current JSON element being processed
|
74
|
-
# @param [RDF::URI] subject
|
75
|
-
# Inherited subject
|
76
|
-
# @param [RDF::URI] property
|
77
|
-
# Inherited property
|
78
|
-
# @param [EvaluationContext] ec
|
79
|
-
# The active context
|
80
|
-
# @return [RDF::Resource] defined by this element
|
81
|
-
# @yield :resource
|
82
|
-
# @yieldparam [RDF::Resource] :resource
|
83
|
-
def traverse(path, element, subject, property, ec)
|
84
|
-
debug(path) {"traverse: e=#{element.class.inspect}, s=#{subject.inspect}, p=#{property.inspect}, e=#{ec.inspect}"}
|
85
|
-
|
86
|
-
traverse_result = case element
|
87
|
-
when Hash
|
88
|
-
# 2.1) If a @context keyword is found, the processor merges each key-value pair in
|
89
|
-
# the local context into the active context ...
|
90
|
-
if element['@context']
|
91
|
-
# Merge context
|
92
|
-
ec = ec.parse(element['@context'])
|
93
|
-
prefixes.merge!(ec.mappings) # Update parsed prefixes
|
94
|
-
end
|
95
|
-
|
96
|
-
# 2.2) Create a copy of the current JSON object, changing keys that map to JSON-LD keywords with those keywords.
|
97
|
-
# Use the new JSON object in subsequent steps
|
98
|
-
new_element = {}
|
99
|
-
element.each do |k, v|
|
100
|
-
k = ec.mapping(k) if ec.mapping(k).to_s[0,1] == '@'
|
101
|
-
new_element[k] = v
|
102
|
-
end
|
103
|
-
unless element == new_element
|
104
|
-
debug(path) {"traverse: keys after map: #{new_element.keys.inspect}"}
|
105
|
-
element = new_element
|
106
|
-
end
|
107
|
-
|
108
|
-
# Other shortcuts to allow use of this method for terminal associative arrays
|
109
|
-
object = if element['@literal']
|
110
|
-
# 2.3) If the JSON object has a @literal key, set the active object to a literal value as follows ...
|
111
|
-
literal_opts = {}
|
112
|
-
literal_opts[:datatype] = ec.expand_iri(element['@type'], :position => :datatype) if element['@type']
|
113
|
-
literal_opts[:language] = element['@language'].to_sym if element['@language']
|
114
|
-
RDF::Literal.new(element['@literal'], literal_opts)
|
115
|
-
elsif element['@list']
|
116
|
-
# 2.4 (Lists)
|
117
|
-
parse_list("#{path}[#{'@list'}]", element['@list'], property, ec) do |resource|
|
118
|
-
add_triple(path, subject, property, resource) if subject && property
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
if object
|
123
|
-
yield object if block_given?
|
124
|
-
return object
|
125
|
-
end
|
126
|
-
|
127
|
-
active_subject = if element['@id'].is_a?(String)
|
128
|
-
# 2.5 Subject
|
129
|
-
# 2.5.1 Set active object (subject)
|
130
|
-
ec.expand_iri(element['@id'], :position => :subject)
|
131
|
-
elsif element['@id']
|
132
|
-
# 2.5.2 Recursively process hash or Array values
|
133
|
-
traverse("#{path}[#{'@id'}]", element['@id'], subject, property, ec) do |resource|
|
134
|
-
add_triple(path, subject, property, resource) if subject && property
|
135
|
-
end
|
136
|
-
else
|
137
|
-
# 2.6) Generate a blank node identifier and set it as the active subject.
|
138
|
-
RDF::Node.new
|
139
|
-
end
|
140
|
-
|
141
|
-
subject = active_subject
|
142
|
-
|
143
|
-
# 2.7) For each key in the JSON object that has not already been processed, perform the following steps:
|
144
|
-
element.each do |key, value|
|
145
|
-
# 2.7.1) If a key that is not @context, @id, or @type, set the active property by
|
146
|
-
# performing Property Processing on the key.
|
147
|
-
property = case key
|
148
|
-
when '@type' then RDF.type
|
149
|
-
when /^@/ then next
|
150
|
-
else ec.expand_iri(key, :position => :predicate)
|
151
|
-
end
|
152
|
-
|
153
|
-
# 2.7.3) List expansion
|
154
|
-
object = if ec.list(property) && value.is_a?(Array)
|
155
|
-
# If the active property is the target of a @list coercion, and the value is an array,
|
156
|
-
# process the value as a list starting at Step 3.1.
|
157
|
-
parse_list("#{path}[#{key}]", value, property, ec) do |resource|
|
158
|
-
# Adds triple for head BNode only, the rest of the list is done within the method
|
159
|
-
add_triple(path, subject, property, resource) if subject && property
|
160
|
-
end
|
161
|
-
else
|
162
|
-
traverse("#{path}[#{key}]", value, subject, property, ec) do |resource|
|
163
|
-
# Adds triples for each value
|
164
|
-
add_triple(path, subject, property, resource) if subject && property
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
# 2.8) The subject is returned
|
170
|
-
subject
|
171
|
-
when Array
|
172
|
-
# 3) If a regular array is detected ...
|
173
|
-
element.each_with_index do |v, i|
|
174
|
-
traverse("#{path}[#{i}]", v, subject, property, ec) do |resource|
|
175
|
-
add_triple(path, subject, property, resource) if subject && property
|
176
|
-
end
|
177
|
-
end
|
178
|
-
nil # No real value returned from an array
|
179
|
-
when String
|
180
|
-
# 4) Perform coersion of the value, or generate a literal
|
181
|
-
debug(path) do
|
182
|
-
"traverse(#{element}): coerce(#{property.inspect}) == #{ec.coerce(property).inspect}, " +
|
183
|
-
"ec=#{ec.coercions.inspect}"
|
184
|
-
end
|
185
|
-
if ec.coerce(property) == '@id'
|
186
|
-
# 4.1) If the active property is the target of a @id coercion ...
|
187
|
-
ec.expand_iri(element, :position => :object)
|
188
|
-
elsif ec.coerce(property)
|
189
|
-
# 4.2) Otherwise, if the active property is the target of coercion ..
|
190
|
-
RDF::Literal.new(element, :datatype => ec.coerce(property))
|
191
|
-
else
|
192
|
-
# 4.3) Otherwise, set the active object to a plain literal value created from the string.
|
193
|
-
RDF::Literal.new(element, :language => ec.language)
|
194
|
-
end
|
195
|
-
when Float
|
196
|
-
object = RDF::Literal::Double.new(element)
|
197
|
-
debug(path) {"traverse(#{element}): native: #{object.inspect}"}
|
198
|
-
object
|
199
|
-
when Fixnum
|
200
|
-
object = RDF::Literal.new(element)
|
201
|
-
debug(path) {"traverse(#{element}): native: #{object.inspect}"}
|
202
|
-
object
|
203
|
-
when TrueClass, FalseClass
|
204
|
-
object = RDF::Literal::Boolean.new(element)
|
205
|
-
debug(path) {"traverse(#{element}): native: #{object.inspect}"}
|
206
|
-
object
|
207
|
-
else
|
208
|
-
raise RDF::ReaderError, "Traverse to unknown element: #{element.inspect} of type #{element.class}"
|
209
|
-
end
|
210
|
-
|
211
|
-
# Yield and return traverse_result
|
212
|
-
yield traverse_result if traverse_result && block_given?
|
213
|
-
traverse_result
|
214
|
-
end
|
215
|
-
|
216
|
-
##
|
217
|
-
# Parse a List
|
218
|
-
#
|
219
|
-
# @param [String] path
|
220
|
-
# location within JSON hash
|
221
|
-
# @param [Array] list
|
222
|
-
# The Array to serialize as a list
|
223
|
-
# @param [RDF::URI] property
|
224
|
-
# Inherited property
|
225
|
-
# @param [EvaluationContext] ec
|
226
|
-
# The active context
|
227
|
-
# @return [RDF::Resource] BNode or nil for head of list
|
228
|
-
# @yield :resource
|
229
|
-
# BNode or nil for head of list
|
230
|
-
# @yieldparam [RDF::Resource] :resource
|
231
|
-
def parse_list(path, list, property, ec)
|
232
|
-
debug(path) {"list: #{list.inspect}, p=#{property.inspect}, e=#{ec.inspect}"}
|
233
|
-
|
234
|
-
last = list.pop
|
235
|
-
result = first_bnode = last ? RDF::Node.new : RDF.nil
|
236
|
-
|
237
|
-
list.each do |list_item|
|
238
|
-
# Traverse the value, using _property_, not rdf:first, to ensure that
|
239
|
-
# proper type coercion is performed
|
240
|
-
traverse("#{path}", list_item, first_bnode, property, ec) do |resource|
|
241
|
-
add_triple("#{path}", first_bnode, RDF.first, resource)
|
242
|
-
end
|
243
|
-
rest_bnode = RDF::Node.new
|
244
|
-
add_triple("#{path}", first_bnode, RDF.rest, rest_bnode)
|
245
|
-
first_bnode = rest_bnode
|
246
|
-
end
|
247
|
-
if last
|
248
|
-
traverse("#{path}", last, first_bnode, property, ec) do |resource|
|
249
|
-
add_triple("#{path}", first_bnode, RDF.first, resource)
|
250
|
-
end
|
251
|
-
add_triple("#{path}", first_bnode, RDF.rest, RDF.nil)
|
252
|
-
end
|
253
|
-
|
254
|
-
yield result if block_given?
|
255
|
-
result
|
256
|
-
end
|
257
|
-
|
258
|
-
##
|
259
|
-
# add a statement, object can be literal or URI or bnode
|
260
|
-
#
|
261
|
-
# @param [String] path
|
262
|
-
# @param [URI, BNode] subject the subject of the statement
|
263
|
-
# @param [URI] predicate the predicate of the statement
|
264
|
-
# @param [URI, BNode, Literal] object the object of the statement
|
265
|
-
# @return [Statement] Added statement
|
266
|
-
# @raise [ReaderError] Checks parameter types and raises if they are incorrect if parsing mode is _validate_.
|
267
|
-
def add_triple(path, subject, predicate, object)
|
268
|
-
predicate = RDF.type if predicate == '@type'
|
269
|
-
statement = RDF::Statement.new(subject, predicate, object)
|
270
|
-
debug(path) {"statement: #{statement.to_ntriples}"}
|
271
|
-
@callback.call(statement)
|
272
|
-
end
|
273
|
-
|
274
|
-
##
|
275
|
-
# Add debug event to debug array, if specified
|
276
|
-
#
|
277
|
-
# @param [XML Node, any] node:: XML Node or string for showing context
|
278
|
-
# @param [String] message
|
279
|
-
# @yieldreturn [String] appended to message, to allow for lazy-evaulation of message
|
280
|
-
def debug(*args)
|
281
|
-
return unless ::JSON::LD.debug? || @options[:debug]
|
282
|
-
message = " " * (@depth || 0) * 2 + (args.empty? ? "" : args.join(": "))
|
283
|
-
message += yield if block_given?
|
284
|
-
puts message if JSON::LD::debug?
|
285
|
-
@options[:debug] << message if @options[:debug].is_a?(Array)
|
286
|
-
end
|
287
63
|
end
|
288
64
|
end
|
289
65
|
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'rdf/nquads'
|
2
|
+
|
3
|
+
module JSON::LD
|
4
|
+
module Triples
|
5
|
+
include Utils
|
6
|
+
|
7
|
+
##
|
8
|
+
#
|
9
|
+
# @param [String] path
|
10
|
+
# location within JSON hash
|
11
|
+
# @param [Hash, Array, String] element
|
12
|
+
# The current JSON element being processed
|
13
|
+
# @param [RDF::Node] subject
|
14
|
+
# Inherited subject
|
15
|
+
# @param [RDF::URI] property
|
16
|
+
# Inherited property
|
17
|
+
# @param [RDF::Node] name
|
18
|
+
# Inherited inherited graph name
|
19
|
+
# @return [RDF::Resource] defined by this element
|
20
|
+
# @yield :statement
|
21
|
+
# @yieldparam [RDF::Statement] :statement
|
22
|
+
def statements(path, element, subject, property, name, &block)
|
23
|
+
debug(path) {"statements: e=#{element.inspect}, s=#{subject.inspect}, p=#{property.inspect}, n=#{name.inspect}"}
|
24
|
+
@node_seq = "jld_t0000" unless subject || property
|
25
|
+
|
26
|
+
traverse_result = depth do
|
27
|
+
case element
|
28
|
+
when Hash
|
29
|
+
# Other shortcuts to allow use of this method for terminal associative arrays
|
30
|
+
object = if element.has_key?('@value')
|
31
|
+
# 1.2) If the JSON object has a @value key, set the active object to a literal value as follows ...
|
32
|
+
literal_opts = {}
|
33
|
+
literal_opts[:datatype] = RDF::URI(element['@type']) if element['@type']
|
34
|
+
literal_opts[:language] = element['@language'].to_sym if element['@language']
|
35
|
+
RDF::Literal.new(element['@value'], literal_opts)
|
36
|
+
elsif element.has_key?('@list')
|
37
|
+
# 1.3 (Lists)
|
38
|
+
parse_list("#{path}[#{'@list'}]", element['@list'], property, name, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
if object
|
42
|
+
# 1.4
|
43
|
+
add_quad(path, subject, property, object, name, &block) if subject && property
|
44
|
+
return object
|
45
|
+
end
|
46
|
+
|
47
|
+
active_subject = if element.fetch('@id', nil).is_a?(String)
|
48
|
+
# 1.5 Subject
|
49
|
+
# 1.5.1 Set active object (subject)
|
50
|
+
context.expand_iri(element['@id'], :quite => true)
|
51
|
+
else
|
52
|
+
# 1.6) Generate a blank node identifier and set it as the active subject.
|
53
|
+
node
|
54
|
+
end
|
55
|
+
|
56
|
+
# 1.7) For each key in the JSON object that has not already been processed,
|
57
|
+
# perform the following steps:
|
58
|
+
element.each do |key, value|
|
59
|
+
active_property = case key
|
60
|
+
when '@type'
|
61
|
+
# If the key is @type, set the active property to rdf:type.
|
62
|
+
RDF.type
|
63
|
+
when '@graph'
|
64
|
+
# Otherwise, if property is @graph, process value algorithm recursively, using active subject
|
65
|
+
# as graph name and null values for active subject and active property and then continue to
|
66
|
+
# next property
|
67
|
+
statements("#{path}[#{key}]", value, nil, nil, active_subject, &block)
|
68
|
+
next
|
69
|
+
when /^@/
|
70
|
+
# Otherwise, if property is a keyword, skip this step.
|
71
|
+
next
|
72
|
+
else
|
73
|
+
# 1.7.1) If a key that is not @id, @graph, or @type, set the active property by
|
74
|
+
# performing Property Processing on the key.
|
75
|
+
context.expand_iri(key, :quite => true)
|
76
|
+
end
|
77
|
+
|
78
|
+
debug("statements[Step 1.7.4]")
|
79
|
+
statements("#{path}[#{key}]", value, active_subject, active_property, name, &block)
|
80
|
+
end
|
81
|
+
|
82
|
+
# 1.8) The active_subject is returned
|
83
|
+
active_subject
|
84
|
+
when Array
|
85
|
+
# 2) If a regular array is detected ...
|
86
|
+
debug("statements[Step 2]")
|
87
|
+
element.each_with_index do |v, i|
|
88
|
+
statements("#{path}[#{i}]", v, subject, property, name, &block)
|
89
|
+
end
|
90
|
+
nil # No real value returned from an array
|
91
|
+
when String
|
92
|
+
object = RDF::Literal.new(element)
|
93
|
+
debug(path) {"statements[Step 3]: plain: #{object.inspect}"}
|
94
|
+
object
|
95
|
+
when Float
|
96
|
+
object = RDF::Literal::Double.new(element)
|
97
|
+
debug(path) {"statements[Step 4]: native: #{object.inspect}"}
|
98
|
+
object
|
99
|
+
when Fixnum
|
100
|
+
object = RDF::Literal.new(element)
|
101
|
+
debug(path) {"statements[Step 5]: native: #{object.inspect}"}
|
102
|
+
object
|
103
|
+
when TrueClass, FalseClass
|
104
|
+
object = RDF::Literal::Boolean.new(element)
|
105
|
+
debug(path) {"statements[Step 6]: native: #{object.inspect}"}
|
106
|
+
object
|
107
|
+
else
|
108
|
+
raise RDF::ReaderError, "Traverse to unknown element: #{element.inspect} of type #{element.class}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Yield and return traverse_result
|
113
|
+
add_quad(path, subject, property, traverse_result, name, &block) if subject && property && traverse_result
|
114
|
+
traverse_result
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# Parse a List
|
119
|
+
#
|
120
|
+
# @param [String] path
|
121
|
+
# location within JSON hash
|
122
|
+
# @param [Array] list
|
123
|
+
# The Array to serialize as a list
|
124
|
+
# @param [RDF::URI] property
|
125
|
+
# Inherited property
|
126
|
+
# @param [RDF::Resource] name
|
127
|
+
# Inherited named graph context
|
128
|
+
# @param [EvaluationContext] ec
|
129
|
+
# The active context
|
130
|
+
# @return [RDF::Resource] BNode or nil for head of list
|
131
|
+
# @yield :statement
|
132
|
+
# @yieldparam [RDF::Statement] :statement
|
133
|
+
def parse_list(path, list, property, name, &block)
|
134
|
+
debug(path) {"list: #{list.inspect}, p=#{property.inspect}, n=#{name.inspect}"}
|
135
|
+
|
136
|
+
last = list.pop
|
137
|
+
result = first_bnode = last ? node : RDF.nil
|
138
|
+
|
139
|
+
depth do
|
140
|
+
list.each do |list_item|
|
141
|
+
# Traverse the value
|
142
|
+
statements("#{path}", list_item, first_bnode, RDF.first, name, &block)
|
143
|
+
rest_bnode = node
|
144
|
+
add_quad("#{path}", first_bnode, RDF.rest, rest_bnode, name, &block)
|
145
|
+
first_bnode = rest_bnode
|
146
|
+
end
|
147
|
+
if last
|
148
|
+
statements("#{path}", last, first_bnode, RDF.first, name, &block)
|
149
|
+
add_quad("#{path}", first_bnode, RDF.rest, RDF.nil, name, &block)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
result
|
153
|
+
end
|
154
|
+
|
155
|
+
##
|
156
|
+
# Create a new named node using the sequence
|
157
|
+
def node
|
158
|
+
n = RDF::Node.new(@node_seq)
|
159
|
+
@node_seq = @node_seq.succ
|
160
|
+
n
|
161
|
+
end
|
162
|
+
|
163
|
+
##
|
164
|
+
# add a statement, object can be literal or URI or bnode
|
165
|
+
#
|
166
|
+
# @param [String] path
|
167
|
+
# @param [RDF::Resource] subject the subject of the statement
|
168
|
+
# @param [RDF::URI] predicate the predicate of the statement
|
169
|
+
# @param [RDF::Term] object the object of the statement
|
170
|
+
# @param [RDF::Resource] name the named graph context of the statement
|
171
|
+
# @yield :statement
|
172
|
+
# @yieldparam [RDF::Statement] :statement
|
173
|
+
def add_quad(path, subject, predicate, object, name)
|
174
|
+
predicate = RDF.type if predicate == '@type'
|
175
|
+
object = RDF::URI(object.to_s) if object.literal? && predicate == RDF.type
|
176
|
+
statement = RDF::Statement.new(subject, predicate, object, :context => name)
|
177
|
+
debug(path) {"statement: #{statement.to_nquads}"}
|
178
|
+
yield statement
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|