json-ld 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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,97 @@
|
|
1
|
+
module JSON::LD
|
2
|
+
module Utils
|
3
|
+
##
|
4
|
+
# Is value a subject? A value is a subject if
|
5
|
+
# * it is a Hash
|
6
|
+
# * it is not a @value, @set or @list
|
7
|
+
# * it has more than 1 key or any key is not @id
|
8
|
+
# @param [Object] value
|
9
|
+
# @return [Boolean]
|
10
|
+
def subject?(value)
|
11
|
+
value.is_a?(Hash) &&
|
12
|
+
(value.keys & %w(@value @list @set)).empty? &&
|
13
|
+
!(value.keys - ['@id']).empty?
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Is value a subject reference?
|
18
|
+
# @param [Object] value
|
19
|
+
# @return [Boolean]
|
20
|
+
def subject_reference?(value)
|
21
|
+
value.is_a?(Hash) && value.keys == %w(@id)
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Is value a blank node? Value is a blank node
|
26
|
+
#
|
27
|
+
# @param [Object] value
|
28
|
+
# @return [Boolean]
|
29
|
+
def blank_node?(value)
|
30
|
+
(subject?(value) || subject_reference?(value)) && value.fetch('@id', '_:')[0,2] == '_:'
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Is value an expaned @list?
|
35
|
+
#
|
36
|
+
# @param [Object] value
|
37
|
+
# @return [Boolean]
|
38
|
+
def list?(value)
|
39
|
+
value.is_a?(Hash) && value.keys == %w(@list)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# Add debug event to debug array, if specified
|
45
|
+
#
|
46
|
+
# @param [String] message
|
47
|
+
# @yieldreturn [String] appended to message, to allow for lazy-evaulation of message
|
48
|
+
def debug(*args)
|
49
|
+
return unless ::JSON::LD.debug? || @options[:debug]
|
50
|
+
list = args
|
51
|
+
list << yield if block_given?
|
52
|
+
message = " " * (@depth || 0) * 2 + (list.empty? ? "" : list.join(": "))
|
53
|
+
puts message if JSON::LD::debug?
|
54
|
+
@options[:debug] << message if @options[:debug].is_a?(Array)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Increase depth around a method invocation
|
58
|
+
def depth(options = {})
|
59
|
+
old_depth = @depth || 0
|
60
|
+
@depth = (options[:depth] || old_depth) + 1
|
61
|
+
ret = yield
|
62
|
+
@depth = old_depth
|
63
|
+
ret
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Utility class for mapping old blank node identifiers, or unnamed blank
|
69
|
+
# nodes to new identifiers
|
70
|
+
class BlankNodeNamer < Hash
|
71
|
+
# @param [String] prefix
|
72
|
+
def initialize(prefix)
|
73
|
+
@prefix = "_:#{prefix}0"
|
74
|
+
super
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Get a new mapped name for `old`
|
79
|
+
#
|
80
|
+
# @param [String] old
|
81
|
+
# @return [String]
|
82
|
+
def get_name(old)
|
83
|
+
if old && self.has_key?(old)
|
84
|
+
self[old]
|
85
|
+
elsif old
|
86
|
+
self[old] = @prefix.dup
|
87
|
+
@prefix.succ!
|
88
|
+
self[old]
|
89
|
+
else
|
90
|
+
# Not referenced, just return a new unique value
|
91
|
+
cur = @prefix.dup
|
92
|
+
@prefix.succ!
|
93
|
+
cur
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/json/ld/writer.rb
CHANGED
@@ -51,6 +51,7 @@ module JSON::LD
|
|
51
51
|
# @see http://json-ld.org/spec/ED/20110507/#the-normalization-algorithm
|
52
52
|
# @author [Gregg Kellogg](http://greggkellogg.net/)
|
53
53
|
class Writer < RDF::Writer
|
54
|
+
include Utils
|
54
55
|
format Format
|
55
56
|
|
56
57
|
# @attr [RDF::Graph] Graph of statements serialized
|
@@ -60,13 +61,9 @@ module JSON::LD
|
|
60
61
|
attr :context
|
61
62
|
|
62
63
|
##
|
63
|
-
#
|
64
|
-
|
65
|
-
|
66
|
-
def self.hash(*args, &block)
|
67
|
-
hash = Hash.new
|
68
|
-
self.new(hash, *args, &block)
|
69
|
-
hash
|
64
|
+
# Override normal symbol generation
|
65
|
+
def self.to_sym
|
66
|
+
:jsonld
|
70
67
|
end
|
71
68
|
|
72
69
|
##
|
@@ -80,33 +77,22 @@ module JSON::LD
|
|
80
77
|
# the encoding to use on the output stream (Ruby 1.9+)
|
81
78
|
# @option options [Boolean] :canonicalize (false)
|
82
79
|
# whether to canonicalize literals when serializing
|
83
|
-
# @option options [Hash] :prefixes (Hash.
|
80
|
+
# @option options [Hash] :prefixes (Hash.ordered)
|
84
81
|
# the prefix mappings to use (not supported by all writers)
|
85
82
|
# @option options [Boolean] :standard_prefixes (false)
|
86
83
|
# Add standard prefixes to @prefixes, if necessary.
|
87
|
-
# @option options [IO, Array, Hash, String, EvaluationContext] :context (Hash.
|
84
|
+
# @option options [IO, Array, Hash, String, EvaluationContext] :context (Hash.ordered)
|
88
85
|
# context to use when serializing. Constructed context for native serialization.
|
89
|
-
# @option options [Boolean] :automatic (true)
|
90
|
-
# Automatically create context coercions and generate compacted form
|
91
|
-
# @option options [Boolean] :expand (false)
|
92
|
-
# Output document in [expanded form](http://json-ld.org/spec/latest/json-ld-api/#expansion)
|
93
|
-
# @option options [Boolean] :compact (false)
|
94
|
-
# Output document in [compacted form](http://json-ld.org/spec/latest/json-ld-api/#compaction).
|
95
|
-
# Requires a referenced evaluation context
|
96
|
-
# @option options [Boolean] :normalize (false)
|
97
|
-
# Output document in [normalized form](http://json-ld.org/spec/latest/json-ld-api/#normalization)
|
98
|
-
# @option options [IO, Array, Hash, String] :frame
|
99
|
-
# Output document in [framed form](http://json-ld.org/spec/latest/json-ld-api/#framing)
|
100
|
-
# using the referenced document as a frame.
|
101
86
|
# @yield [writer] `self`
|
102
87
|
# @yieldparam [RDF::Writer] writer
|
103
88
|
# @yieldreturn [void]
|
104
89
|
# @yield [writer]
|
105
90
|
# @yieldparam [RDF::Writer] writer
|
106
91
|
def initialize(output = $stdout, options = {}, &block)
|
92
|
+
options[:base_uri] ||= options[:base] if options.has_key?(:base)
|
93
|
+
options[:base] ||= options[:base_uri] if options.has_key?(:base_uri)
|
107
94
|
super do
|
108
95
|
@graph = RDF::Graph.new
|
109
|
-
@options[:automatic] = true unless [:automatic, :expand, :compact, :frame, :normalize].any? {|k| options.has_key?(k)}
|
110
96
|
|
111
97
|
if block_given?
|
112
98
|
case block.arity
|
@@ -128,7 +114,7 @@ module JSON::LD
|
|
128
114
|
end
|
129
115
|
|
130
116
|
##
|
131
|
-
#
|
117
|
+
# Adds a statement to be serialized
|
132
118
|
# @param [RDF::Statement] statement
|
133
119
|
# @return [void]
|
134
120
|
def write_statement(statement)
|
@@ -148,464 +134,40 @@ module JSON::LD
|
|
148
134
|
end
|
149
135
|
|
150
136
|
##
|
151
|
-
# Outputs the Serialized JSON-LD representation of all stored
|
137
|
+
# Outputs the Serialized JSON-LD representation of all stored statements.
|
138
|
+
#
|
139
|
+
# If provided a context or prefixes, we'll create a context
|
140
|
+
# and use it to compact the output. Otherwise, we return un-compacted JSON-LD
|
152
141
|
#
|
153
142
|
# @return [void]
|
154
143
|
# @see #write_triple
|
155
144
|
def write_epilogue
|
156
145
|
@debug = @options[:debug]
|
157
146
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
# Update prefix mappings to those defined in context
|
174
|
-
@options[:prefixes] = {}
|
175
|
-
@context.iri_to_term.each_pair do |iri, term|
|
176
|
-
debug {"add prefix #{term.inspect} for #{iri}"}
|
177
|
-
prefix(term, iri) # Define for output
|
178
|
-
end
|
179
|
-
|
180
|
-
# Don't generate context for expanded or normalized output
|
181
|
-
json_hash = (@options[:expand] || @options[:normalize]) ? Hash.new : context.serialize(:depth => @depth)
|
182
|
-
|
183
|
-
elements = []
|
184
|
-
order_subjects.each do |subject|
|
185
|
-
unless is_done?(subject)
|
186
|
-
elements << subject(subject, json_hash)
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
return if elements.empty?
|
191
|
-
|
192
|
-
# If there are more than one top-level subjects, place in an array form
|
193
|
-
if elements.length == 1 && elements.first.is_a?(Hash)
|
194
|
-
json_hash.merge!(elements.first)
|
195
|
-
else
|
196
|
-
json_hash['@id'] = elements
|
197
|
-
end
|
198
|
-
|
199
|
-
if @output.is_a?(Hash)
|
200
|
-
@output.merge!(json_hash)
|
201
|
-
else
|
202
|
-
json_state = if @options[:normalize]
|
203
|
-
# Normalization uses a compressed form
|
204
|
-
JSON::State.new(
|
205
|
-
:indent => "",
|
206
|
-
:space => "",
|
207
|
-
:space_before => "",
|
208
|
-
:object_nl => "",
|
209
|
-
:array_nl => ""
|
210
|
-
)
|
211
|
-
else
|
212
|
-
JSON::State.new(
|
213
|
-
:indent => " ",
|
214
|
-
:space => " ",
|
215
|
-
:space_before => "",
|
216
|
-
:object_nl => "\n",
|
217
|
-
:array_nl => "\n"
|
218
|
-
)
|
219
|
-
end
|
220
|
-
@output.write(json_hash.to_json(json_state))
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
##
|
225
|
-
# Returns the representation of a IRI reference.
|
226
|
-
#
|
227
|
-
# Spec confusion: should a subject IRI be normalized?
|
228
|
-
#
|
229
|
-
# @param [RDF::URI] value
|
230
|
-
# @param [Hash{Symbol => Object}] options
|
231
|
-
# @option options [:subject, :predicate, :object] position
|
232
|
-
# Useful when determining how to serialize.
|
233
|
-
# @option options [RDF::URI] property
|
234
|
-
# Property for object reference, which can be used to return bare strings
|
235
|
-
# @return [Object]
|
236
|
-
def format_iri(value, options = {})
|
237
|
-
debug {"format_iri(#{options.inspect}, #{value.inspect})"}
|
238
|
-
|
239
|
-
result = context.compact_iri(value, {:depth => @depth}.merge(options))
|
240
|
-
unless options[:position] != :object || iri_range?(options[:property])
|
241
|
-
result = {"@id" => result}
|
242
|
-
end
|
243
|
-
|
244
|
-
debug {"=> #{result.inspect}"}
|
245
|
-
result
|
246
|
-
end
|
247
|
-
|
248
|
-
##
|
249
|
-
# @param [RDF::Node] value
|
250
|
-
# @param [Hash{Symbol => Object}] options
|
251
|
-
# @return [String]
|
252
|
-
# @raise [NotImplementedError] unless implemented in subclass
|
253
|
-
# @see {#format\_iri}
|
254
|
-
def format_node(value, options = {})
|
255
|
-
format_iri(value, options)
|
256
|
-
end
|
257
|
-
|
258
|
-
##
|
259
|
-
# Returns the representation of a literal.
|
260
|
-
#
|
261
|
-
# @param [RDF::Literal, String, #to_s] literal
|
262
|
-
# @param [Hash{Symbol => Object}] options
|
263
|
-
# @option options [RDF::URI] property
|
264
|
-
# Property referencing literal for type coercion
|
265
|
-
# @return [Object]
|
266
|
-
def format_literal(literal, options = {})
|
267
|
-
debug {"format_literal(#{options.inspect}, #{literal.inspect})"}
|
268
|
-
|
269
|
-
value = Hash.new
|
270
|
-
value['@literal'] = literal.value
|
271
|
-
value['@type'] = literal.datatype.to_s if literal.has_datatype?
|
272
|
-
value['@language'] = literal.language.to_s if literal.has_language?
|
273
|
-
|
274
|
-
result = case literal
|
275
|
-
when RDF::Literal::Boolean, RDF::Literal::Integer, RDF::Literal::Double
|
276
|
-
literal.object
|
277
|
-
else
|
278
|
-
context.compact_value(options[:property], value, {:depth => @depth}.merge(options))
|
279
|
-
end
|
280
|
-
|
281
|
-
debug {"=> #{result.inspect}"}
|
282
|
-
result
|
283
|
-
end
|
284
|
-
|
285
|
-
##
|
286
|
-
# Serialize an RDF list
|
287
|
-
#
|
288
|
-
# @param [RDF::URI] object
|
289
|
-
# @param [Hash{Symbol => Object}] options
|
290
|
-
# @option options [RDF::URI] property
|
291
|
-
# Property referencing literal for type and list coercion
|
292
|
-
# @return [Hash{"@list" => Array<Object>}]
|
293
|
-
def format_list(object, options = {})
|
294
|
-
predicate = options[:property]
|
295
|
-
list = RDF::List.new(object, @graph)
|
296
|
-
ary = []
|
297
|
-
|
298
|
-
debug {"format_list(#{list.inspect}, #{predicate})"}
|
299
|
-
|
300
|
-
depth do
|
301
|
-
list.each_statement do |st|
|
302
|
-
next unless st.predicate == RDF.first
|
303
|
-
debug {" format_list this: #{st.subject} first: #{st.object}"}
|
304
|
-
ary << if predicate || st.object.literal?
|
305
|
-
property(predicate, st.object)
|
306
|
-
else
|
307
|
-
subject(st.object)
|
308
|
-
end
|
309
|
-
subject_done(st.subject)
|
310
|
-
end
|
311
|
-
end
|
312
|
-
|
313
|
-
# Returns
|
314
|
-
ary = {'@list' => ary} unless predicate && list_range?(predicate)
|
315
|
-
debug {"format_list => #{ary.inspect}"}
|
316
|
-
ary
|
317
|
-
end
|
318
|
-
|
319
|
-
private
|
320
|
-
# Perform any preprocessing of statements required
|
321
|
-
def preprocess
|
322
|
-
@graph.each {|statement| preprocess_statement(statement)}
|
323
|
-
end
|
324
|
-
|
325
|
-
# Perform any statement preprocessing required. This is used to perform reference counts and determine required
|
326
|
-
# prefixes.
|
327
|
-
#
|
328
|
-
# @param [Statement] statement
|
329
|
-
def preprocess_statement(statement)
|
330
|
-
debug {"preprocess: #{statement.inspect}"}
|
331
|
-
references = ref_count(statement.object) + 1
|
332
|
-
@references[statement.object] = references
|
333
|
-
@subjects[statement.subject] = true
|
334
|
-
|
335
|
-
depth do
|
336
|
-
# Pre-fetch qnames, to fill prefixes
|
337
|
-
format_iri(statement.subject, :position => :subject)
|
338
|
-
format_iri(statement.predicate, :position => :predicate)
|
339
|
-
|
340
|
-
# To figure out coercion requirements
|
341
|
-
if statement.object.literal?
|
342
|
-
format_literal(statement.object, :property => statement.predicate)
|
343
|
-
datatype_range?(statement.predicate)
|
344
|
-
else
|
345
|
-
format_iri(statement.object, :position => :object)
|
346
|
-
iri_range?(statement.predicate)
|
347
|
-
end
|
348
|
-
list_range?(statement.predicate)
|
349
|
-
end
|
350
|
-
|
351
|
-
@references[statement.predicate] = ref_count(statement.predicate) + 1
|
352
|
-
end
|
353
|
-
|
354
|
-
# Serialize a subject
|
355
|
-
# Option contains referencing property, if this is recursive
|
356
|
-
# @return [Hash]
|
357
|
-
def subject(subject, options = {})
|
358
|
-
defn = Hash.new
|
359
|
-
|
360
|
-
raise RDF::WriterError, "Illegal use of subject #{subject.inspect}, not supported" unless subject.resource?
|
361
|
-
|
362
|
-
subject_done(subject)
|
363
|
-
properties = @graph.properties(subject)
|
364
|
-
debug {"subject: #{subject.inspect}, props: #{properties.inspect}"}
|
365
|
-
|
366
|
-
@graph.query(:subject => subject).each do |st|
|
367
|
-
raise RDF::WriterError, "Illegal use of predicate #{st.predicate.inspect}, not supported in RDF/XML" unless st.predicate.uri?
|
368
|
-
end
|
369
|
-
|
370
|
-
if subject.node? && ref_count(subject) > (options[:property] ? 1 : 0) && options[:expand]
|
371
|
-
raise RDF::WriterError, "Can't serialize named node when normalizing"
|
372
|
-
end
|
373
|
-
|
374
|
-
# Subject may be a list
|
375
|
-
if is_valid_list?(subject)
|
376
|
-
debug "subject is a list"
|
377
|
-
defn['@id'] = format_list(subject)
|
378
|
-
properties.delete(RDF.first.to_s)
|
379
|
-
properties.delete(RDF.rest.to_s)
|
380
|
-
|
381
|
-
# Special case, if there are no properties, then we can just serialize the list itself
|
382
|
-
return defn if properties.empty?
|
383
|
-
elsif subject.uri? || ref_count(subject) > 1
|
384
|
-
debug "subject is an iri or it's a node referenced multiple times"
|
385
|
-
# Don't need to set subject if it's a Node without references
|
386
|
-
defn['@id'] = format_iri(subject, :position => :subject)
|
387
|
-
else
|
388
|
-
debug "subject is an unreferenced BNode"
|
389
|
-
end
|
390
|
-
|
391
|
-
prop_list = order_properties(properties)
|
392
|
-
debug {"=> property order: #{prop_list.inspect}"}
|
393
|
-
|
394
|
-
prop_list.each do |prop|
|
395
|
-
predicate = RDF::URI.intern(prop)
|
396
|
-
|
397
|
-
p_iri = format_iri(predicate, :position => :predicate)
|
398
|
-
depth do
|
399
|
-
defn[p_iri] = property(predicate, properties[prop])
|
400
|
-
debug {"prop(#{p_iri}) => #{properties[prop]} => #{defn[p_iri].inspect}"}
|
401
|
-
end
|
402
|
-
end
|
403
|
-
|
404
|
-
debug {"subject: #{subject} has defn: #{defn.inspect}"}
|
405
|
-
defn
|
406
|
-
end
|
407
|
-
|
408
|
-
##
|
409
|
-
# Serialize objects for a property
|
410
|
-
#
|
411
|
-
# Spec confusion: sorting of multi-valued properties not adequately specified.
|
412
|
-
#
|
413
|
-
# @param [RDF::URI] predicate
|
414
|
-
# @param [Array<RDF::URI>, RDF::URI] objects
|
415
|
-
# @param [Hash{Symbol => Object}] options
|
416
|
-
# @return [Array, Hash, Object]
|
417
|
-
def property(predicate, objects, options = {})
|
418
|
-
objects = objects.first if objects.is_a?(Array) && objects.length == 1
|
419
|
-
case objects
|
420
|
-
when Array
|
421
|
-
objects.sort_by(&:to_s).map {|o| property(predicate, o, options)}
|
422
|
-
when RDF::Literal
|
423
|
-
format_literal(objects, options.merge(:property => predicate))
|
424
|
-
else
|
425
|
-
if is_valid_list?(objects)
|
426
|
-
format_list(objects, :property => predicate)
|
427
|
-
elsif is_done?(objects) || !@subjects.include?(objects)
|
428
|
-
format_iri(objects, :position => :object, :property => predicate)
|
429
|
-
else
|
430
|
-
subject(objects, :property => predicate)
|
431
|
-
end
|
432
|
-
end
|
433
|
-
end
|
434
|
-
|
435
|
-
##
|
436
|
-
# Take a hash from predicate IRIs to lists of values.
|
437
|
-
# Sort the lists of values. Return a sorted list of properties.
|
438
|
-
# @param [Hash{String => Array<Resource>}] properties A hash of Property to Resource mappings
|
439
|
-
# @return [Array<String>}] Ordered list of properties.
|
440
|
-
def order_properties(properties)
|
441
|
-
# Make sorted list of properties
|
442
|
-
prop_list = []
|
443
|
-
|
444
|
-
properties.keys.sort do |a, b|
|
445
|
-
format_iri(a, :position => :predicate) <=> format_iri(b, :position => :predicate)
|
446
|
-
end.each do |prop|
|
447
|
-
prop_list << prop.to_s
|
448
|
-
end
|
449
|
-
|
450
|
-
prop_list
|
451
|
-
end
|
452
|
-
|
453
|
-
# Order subjects for output. Override this to output subjects in another order.
|
454
|
-
#
|
455
|
-
# @return [Array<Resource>] Ordered list of subjects
|
456
|
-
def order_subjects
|
457
|
-
seen = {}
|
458
|
-
subjects = []
|
459
|
-
|
460
|
-
return @subjects.keys.sort do |a,b|
|
461
|
-
format_iri(a, :position => :subject) <=> format_iri(b, :position => :subject)
|
462
|
-
end unless @options[:automatic]
|
463
|
-
|
464
|
-
# Sort subjects by resources over bnodes, ref_counts and the subject URI itself
|
465
|
-
recursable = @subjects.keys.
|
466
|
-
select {|s| !seen.include?(s)}.
|
467
|
-
map {|r| [r.is_a?(RDF::Node) ? 1 : 0, ref_count(r), r]}.
|
468
|
-
sort
|
469
|
-
|
470
|
-
subjects += recursable.map{|r| r.last}
|
471
|
-
end
|
472
|
-
|
473
|
-
# Return the number of times this node has been referenced in the object position
|
474
|
-
# @return [Integer]
|
475
|
-
def ref_count(node)
|
476
|
-
@references.fetch(node, 0)
|
477
|
-
end
|
478
|
-
|
479
|
-
##
|
480
|
-
# Does predicate have a range of IRI?
|
481
|
-
# @param [RDF::URI] predicate
|
482
|
-
# @return [Boolean]
|
483
|
-
def iri_range?(predicate)
|
484
|
-
return false if predicate.nil? || [RDF.first, RDF.rest].include?(predicate) || @options[:expand]
|
485
|
-
return true if predicate == RDF.type
|
486
|
-
|
487
|
-
unless context.coerce(predicate)
|
488
|
-
not_iri = !@options[:automatic]
|
489
|
-
#debug {" (automatic) = #{(!not_iri).inspect}"}
|
490
|
-
|
491
|
-
# Any literal object makes it not so
|
492
|
-
not_iri ||= @graph.query(:predicate => predicate).to_a.any? do |st|
|
493
|
-
l = RDF::List.new(st.object, @graph)
|
494
|
-
#debug {" o.literal? #{st.object.literal?.inspect}"}
|
495
|
-
#debug {" l.valid? #{l.valid?.inspect}"}
|
496
|
-
#debug {" l.any.valid? #{l.to_a.any?(&:literal?).inspect}"}
|
497
|
-
st.object.literal? || (l.valid? && l.to_a.any?(&:literal?))
|
498
|
-
end
|
499
|
-
#debug {" (literal) = #{(!not_iri).inspect}"}
|
500
|
-
|
501
|
-
# FIXME: detect when values are all represented through chaining
|
502
|
-
|
503
|
-
context.coerce(predicate, not_iri ? false : '@id')
|
147
|
+
# Turn graph into a triple array
|
148
|
+
statements = @graph.each_statement.to_a
|
149
|
+
debug("writer") { "serialize #{statements.length} statements, #{@options.inspect}"}
|
150
|
+
result = API.fromRDF(statements, nil, @options)
|
151
|
+
|
152
|
+
# If we were provided a context, or prefixes, use them to compact the output
|
153
|
+
context = RDF::Util::File.open_file(@options[:context]) if @options[:context].is_a?(String)
|
154
|
+
context ||= @options[:context]
|
155
|
+
context ||= if @options[:prefixes] || @options[:language] || @options[:standard_prefixes]
|
156
|
+
ctx = EvaluationContext.new(@options)
|
157
|
+
ctx.language = @options[:language] if @options[:language]
|
158
|
+
@options[:prefixes].each do |prefix, iri|
|
159
|
+
ctx.set_mapping(prefix, iri) if prefix && iri
|
160
|
+
end if @options[:prefixes]
|
161
|
+
ctx
|
504
162
|
end
|
505
163
|
|
506
|
-
|
507
|
-
context
|
508
|
-
|
509
|
-
|
510
|
-
##
|
511
|
-
# Does predicate have a range of specific typed literal?
|
512
|
-
# @param [RDF::URI] predicate
|
513
|
-
# @return [Boolean]
|
514
|
-
def datatype_range?(predicate)
|
515
|
-
unless context.coerce(predicate)
|
516
|
-
# objects of all statements with the predicate must be literal
|
517
|
-
# and have the same non-nil datatype
|
518
|
-
dt = nil
|
519
|
-
if @options[:automatic]
|
520
|
-
@graph.query(:predicate => predicate) do |st|
|
521
|
-
debug {"datatype_range? literal? #{st.object.literal?.inspect} dt? #{(st.object.literal? && st.object.has_datatype?).inspect}"}
|
522
|
-
if st.object.literal? && st.object.has_datatype?
|
523
|
-
dt = st.object.datatype.to_s if dt.nil?
|
524
|
-
debug {"=> dt: #{st.object.datatype}"}
|
525
|
-
dt = false unless dt == st.object.datatype.to_s
|
526
|
-
else
|
527
|
-
dt = false
|
528
|
-
end
|
529
|
-
end
|
530
|
-
# Cause necessary prefixes to be output
|
531
|
-
format_iri(dt, :position => :datatype) if dt && !NATIVE_DATATYPES.include?(dt.to_s)
|
532
|
-
debug {"range(#{predicate}) = #{dt.inspect}"}
|
533
|
-
else
|
534
|
-
dt = false
|
535
|
-
end
|
536
|
-
context.coerce(predicate, dt)
|
164
|
+
# Perform compaction, if we have a context
|
165
|
+
if context
|
166
|
+
debug("writer") { "compact result"}
|
167
|
+
result = API.compact(result, context, nil, @options)
|
537
168
|
end
|
538
169
|
|
539
|
-
|
540
|
-
end
|
541
|
-
|
542
|
-
##
|
543
|
-
# Is every use of the predicate an RDF Collection?
|
544
|
-
#
|
545
|
-
# @param [RDF::URI] predicate
|
546
|
-
# @return [Boolean]
|
547
|
-
def list_range?(predicate)
|
548
|
-
return false if [RDF.first, RDF.rest].include?(predicate)
|
549
|
-
|
550
|
-
unless @list_range.include?(predicate.to_s)
|
551
|
-
# objects of all statements with the predicate must be a list
|
552
|
-
@list_range[predicate.to_s] = if @options[:automatic]
|
553
|
-
@graph.query(:predicate => predicate).to_a.all? do |st|
|
554
|
-
is_valid_list?(st.object)
|
555
|
-
end
|
556
|
-
else
|
557
|
-
false
|
558
|
-
end
|
559
|
-
context.list(predicate, true) if @list_range[predicate.to_s]
|
560
|
-
|
561
|
-
debug {"list(#{predicate}) = #{@list_range[predicate.to_s].inspect}"}
|
562
|
-
end
|
563
|
-
|
564
|
-
@list_range[predicate.to_s]
|
565
|
-
end
|
566
|
-
|
567
|
-
# Reset internal helper instance variables
|
568
|
-
def reset
|
569
|
-
@depth = 0
|
570
|
-
@references = {}
|
571
|
-
@serialized = {}
|
572
|
-
@subjects = {}
|
573
|
-
@list_range = {}
|
574
|
-
end
|
575
|
-
|
576
|
-
# Checks if l is a valid RDF list, i.e. no nodes have other properties.
|
577
|
-
def is_valid_list?(l)
|
578
|
-
#debug {"is_valid_list: #{l.inspect}"}
|
579
|
-
return RDF::List.new(l, @graph).valid?
|
580
|
-
end
|
581
|
-
|
582
|
-
def is_done?(subject)
|
583
|
-
@serialized.include?(subject)
|
584
|
-
end
|
585
|
-
|
586
|
-
# Mark a subject as done.
|
587
|
-
def subject_done(subject)
|
588
|
-
@serialized[subject] = true
|
589
|
-
end
|
590
|
-
|
591
|
-
# Add debug event to debug array, if specified
|
592
|
-
#
|
593
|
-
# @param [String] message
|
594
|
-
# @yieldreturn [String] appended to message, to allow for lazy-evaulation of message
|
595
|
-
def debug(*args)
|
596
|
-
return unless ::JSON::LD.debug? || @options[:debug]
|
597
|
-
message = " " * @depth * 2 + (args.empty? ? "" : args.join(": "))
|
598
|
-
message += yield if block_given?
|
599
|
-
puts message if JSON::LD::debug?
|
600
|
-
@options[:debug] << message if @options[:debug].is_a?(Array)
|
601
|
-
end
|
602
|
-
|
603
|
-
# Increase depth around a method invocation
|
604
|
-
def depth
|
605
|
-
@depth += 1
|
606
|
-
ret = yield
|
607
|
-
@depth -= 1
|
608
|
-
ret
|
170
|
+
@output.write(result.to_json(JSON_STATE))
|
609
171
|
end
|
610
172
|
end
|
611
173
|
end
|