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.
@@ -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
@@ -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
- # Return the pre-serialized Hash before turning into JSON
64
- #
65
- # @return [Hash]
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.new)
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.new)
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
- # Addes a statement to be serialized
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 triples.
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
- reset
159
-
160
- raise RDF::WriterError, "Compaction requres a context" if @options[:compact] && !@options[:context]
161
-
162
- @context = EvaluationContext.new(@options)
163
- @context = @context.parse(@options[:context]) if @options[:context]
164
- @context.language = @options[:language] if @options[:language]
165
- @context.lists.each {|p| @list_range[p] = true}
166
-
167
- debug {"\nserialize: graph: #{@graph.size}"}
168
- debug {"=> options: #{@options.reject {|k,v| k == :debug}.inspect}"}
169
- debug {"=> context: #{@context.inspect}"}
170
-
171
- preprocess
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
- debug {"iri_range(#{predicate}) = #{context.coerce(predicate).inspect}"}
507
- context.coerce(predicate) == '@id'
508
- end
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
- context.coerce(predicate)
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