json-ld 0.0.8 → 0.1.0

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.
@@ -34,10 +34,8 @@ module JSON::LD
34
34
  #
35
35
  # The writer will add prefix definitions, and use them for creating @context definitions, and minting CURIEs
36
36
  #
37
- # @example Creating @base, @vocab and @context prefix definitions in output
37
+ # @example Creating @@context prefix definitions in output
38
38
  # JSON::LD::Writer.buffer(
39
- # :base_uri => "http://example.com/",
40
- # :vocab => "http://example.net/"
41
39
  # :prefixes => {
42
40
  # nil => "http://example.com/ns#",
43
41
  # :foaf => "http://xmlns.com/foaf/0.1/"}
@@ -47,7 +45,7 @@ module JSON::LD
47
45
  # end
48
46
  # end
49
47
  #
50
- # Select the :normalize option to output JSON-LD in canonical form
48
+ # Select the :expand option to output JSON-LD in expanded form
51
49
  #
52
50
  # @see http://json-ld.org/spec/ED/20110507/
53
51
  # @see http://json-ld.org/spec/ED/20110507/#the-normalization-algorithm
@@ -55,39 +53,18 @@ module JSON::LD
55
53
  class Writer < RDF::Writer
56
54
  format Format
57
55
 
58
- # @attr [Graph] Graph of statements serialized
56
+ # @attr [RDF::Graph] Graph of statements serialized
59
57
  attr :graph
60
- # @attr [URI] Base IRI used for relativizing IRIs
61
- attr :base_uri
62
- # @attr [String] Vocabulary prefix used for relativizing IRIs
63
- attr :vocab
64
-
65
- # Type coersion to use for serialization. Defaults to DEFAULT_COERCION
66
- #
67
- # Maintained as a reverse mapping of `property` => `type`.
68
- #
69
- # @attr [Hash{String => String}]
70
- attr :coerce, true
71
-
72
- ##
73
- # Local implementation of ruby Hash class to allow for ordering in 1.8.x implementations.
74
- #
75
- # @return [Hash, InsertOrderPreservingHash]
76
- def self.new_hash
77
- if RUBY_VERSION < "1.9"
78
- InsertOrderPreservingHash.new
79
- else
80
- Hash.new
81
- end
82
- end
83
- def new_hash; self.class.new_hash; end
58
+
59
+ # @attr [EvaluationContext] context used to load and administer contexts
60
+ attr :context
84
61
 
85
62
  ##
86
63
  # Return the pre-serialized Hash before turning into JSON
87
64
  #
88
65
  # @return [Hash]
89
66
  def self.hash(*args, &block)
90
- hash = new_hash
67
+ hash = Hash.new
91
68
  self.new(hash, *args, &block)
92
69
  hash
93
70
  end
@@ -103,16 +80,24 @@ module JSON::LD
103
80
  # the encoding to use on the output stream (Ruby 1.9+)
104
81
  # @option options [Boolean] :canonicalize (false)
105
82
  # whether to canonicalize literals when serializing
106
- # @option options [Boolean] :normalize (false)
107
- # Output document in [normalized form](http://json-ld.org/spec/latest/#normalization-1)
108
83
  # @option options [Hash] :prefixes (Hash.new)
109
84
  # the prefix mappings to use (not supported by all writers)
110
- # @option options [#to_s] :base_uri (nil)
111
- # Base IRI used for relativizing IRIs
112
- # @option options [#to_s] :vocab (nil)
113
- # Vocabulary prefix used for relativizing IRIs
114
85
  # @option options [Boolean] :standard_prefixes (false)
115
86
  # Add standard prefixes to @prefixes, if necessary.
87
+ # @option options [IO, Array, Hash, String, EvaluationContext] :context (Hash.new)
88
+ # 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.
116
101
  # @yield [writer] `self`
117
102
  # @yieldparam [RDF::Writer] writer
118
103
  # @yieldreturn [void]
@@ -121,8 +106,8 @@ module JSON::LD
121
106
  def initialize(output = $stdout, options = {}, &block)
122
107
  super do
123
108
  @graph = RDF::Graph.new
124
- @iri_to_prefix = DEFAULT_CONTEXT.dup.delete_if {|k,v| k == '@coerce'}.invert
125
- @coerce = DEFAULT_COERCE.merge(options[:coerce] || {})
109
+ @options[:automatic] = true unless [:automatic, :expand, :compact, :frame, :normalize].any? {|k| options.has_key?(k)}
110
+
126
111
  if block_given?
127
112
  case block.arity
128
113
  when 0 then instance_eval(&block)
@@ -138,7 +123,7 @@ module JSON::LD
138
123
  # @param [Graph] graph
139
124
  # @return [void]
140
125
  def write_graph(graph)
141
- add_debug {"Add graph #{graph.inspect}"}
126
+ debug {"Add graph #{graph.inspect}"}
142
127
  @graph = graph
143
128
  end
144
129
 
@@ -168,18 +153,32 @@ module JSON::LD
168
153
  # @return [void]
169
154
  # @see #write_triple
170
155
  def write_epilogue
171
- @base_uri = RDF::URI(@options[:base_uri]) if @options[:base_uri] && !@options[:normalize]
172
- @vocab = @options[:vocab] unless @options[:normalize]
173
156
  @debug = @options[:debug]
174
157
 
175
158
  reset
159
+
160
+ raise RDF::WriterError, "Compaction requres a context" if @options[:compact] && !@options[:context]
176
161
 
177
- add_debug {"\nserialize: graph: #{@graph.size}"}
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}"}
178
170
 
179
171
  preprocess
180
-
181
- # Don't generate context for canonical output
182
- json_hash = @options[:normalize] ? new_hash : start_document
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)
183
182
 
184
183
  elements = []
185
184
  order_subjects.each do |subject|
@@ -190,16 +189,18 @@ module JSON::LD
190
189
 
191
190
  return if elements.empty?
192
191
 
192
+ # If there are more than one top-level subjects, place in an array form
193
193
  if elements.length == 1 && elements.first.is_a?(Hash)
194
194
  json_hash.merge!(elements.first)
195
195
  else
196
- json_hash['@subject'] = elements
196
+ json_hash['@id'] = elements
197
197
  end
198
198
 
199
199
  if @output.is_a?(Hash)
200
200
  @output.merge!(json_hash)
201
201
  else
202
202
  json_state = if @options[:normalize]
203
+ # Normalization uses a compressed form
203
204
  JSON::State.new(
204
205
  :indent => "",
205
206
  :space => "",
@@ -223,35 +224,24 @@ module JSON::LD
223
224
  ##
224
225
  # Returns the representation of a IRI reference.
225
226
  #
226
- # Spec confusion: should a subject URI be normalized?
227
+ # Spec confusion: should a subject IRI be normalized?
227
228
  #
228
229
  # @param [RDF::URI] value
229
230
  # @param [Hash{Symbol => Object}] options
230
231
  # @option options [:subject, :predicate, :object] position
231
232
  # Useful when determining how to serialize.
232
233
  # @option options [RDF::URI] property
233
- # Property for object reference, which can be used to return
234
- # bare strings, rather than {"iri":}
234
+ # Property for object reference, which can be used to return bare strings
235
235
  # @return [Object]
236
- def format_uri(value, options = {})
237
- result = case options[:position]
238
- when :subject
239
- # attempt base_uri replacement
240
- short = value.to_s.sub(base_uri.to_s, "")
241
- short == value.to_s ? (get_curie(value) || value.to_s) : short
242
- when :predicate
243
- # attempt vocab replacement
244
- short = '@type' if value == RDF.type
245
- short ||= value.to_s.sub(@vocab.to_s, "")
246
- short == value.to_s ? (get_curie(value) || value.to_s) : short
247
- else
248
- # Encode like a subject
249
- iri_range?(options[:property]) ?
250
- format_uri(value, :position => :subject) :
251
- {'@iri' => format_uri(value, :position => :subject)}
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}
252
242
  end
253
243
 
254
- add_debug {"format_uri(#{options.inspect}, #{value.inspect}) => #{result.inspect}"}
244
+ debug {"=> #{result.inspect}"}
255
245
  result
256
246
  end
257
247
 
@@ -260,9 +250,9 @@ module JSON::LD
260
250
  # @param [Hash{Symbol => Object}] options
261
251
  # @return [String]
262
252
  # @raise [NotImplementedError] unless implemented in subclass
263
- # @abstract
253
+ # @see {#format\_iri}
264
254
  def format_node(value, options = {})
265
- format_uri(value, options)
255
+ format_iri(value, options)
266
256
  end
267
257
 
268
258
  ##
@@ -274,137 +264,88 @@ module JSON::LD
274
264
  # Property referencing literal for type coercion
275
265
  # @return [Object]
276
266
  def format_literal(literal, options = {})
277
- if options[:normal] || @options[:normalize]
278
- ret = new_hash
279
- ret['@literal'] = literal.value
280
- ret['@datatype'] = format_uri(literal.datatype, :position => :subject) if literal.has_datatype?
281
- ret['@language'] = literal.language.to_s if literal.has_language?
282
- return ret.delete_if {|k,v| v.nil?}
283
- end
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?
284
273
 
285
- case literal
286
- when RDF::Literal::Integer, RDF::Literal::Boolean
274
+ result = case literal
275
+ when RDF::Literal::Boolean, RDF::Literal::Integer, RDF::Literal::Double
287
276
  literal.object
288
- when RDF::Literal
289
- if datatype_range?(options[:property]) || !(literal.has_datatype? || literal.has_language?)
290
- # Datatype coercion where literal has the same datatype
291
- literal.value
292
- else
293
- format_literal(literal, :normal => true)
294
- end
277
+ else
278
+ context.compact_value(options[:property], value, {:depth => @depth}.merge(options))
295
279
  end
280
+
281
+ debug {"=> #{result.inspect}"}
282
+ result
296
283
  end
297
284
 
298
285
  ##
299
286
  # Serialize an RDF list
287
+ #
300
288
  # @param [RDF::URI] object
301
289
  # @param [Hash{Symbol => Object}] options
302
290
  # @option options [RDF::URI] property
303
- # Property referencing literal for type coercion
291
+ # Property referencing literal for type and list coercion
304
292
  # @return [Hash{"@list" => Array<Object>}]
305
293
  def format_list(object, options = {})
306
294
  predicate = options[:property]
307
- list = []
295
+ list = RDF::List.new(object, @graph)
296
+ ary = []
308
297
 
309
- add_debug {"format_list(#{object}, #{predicate})"}
298
+ debug {"format_list(#{list.inspect}, #{predicate})"}
310
299
 
311
- @depth += 1
312
- while object do
313
- subject_done(object)
314
- p = @graph.properties(object)
315
- item = p.fetch(RDF.first.to_s, []).first
316
- if item
317
- add_debug {"format_list serialize #{item.inspect}"}
318
- list << if predicate || item.literal?
319
- property(predicate, item)
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)
320
306
  else
321
- subject(item)
307
+ subject(st.object)
322
308
  end
309
+ subject_done(st.subject)
323
310
  end
324
- object = p.fetch(RDF.rest.to_s, []).first
325
311
  end
326
- @depth -= 1
327
312
 
328
- # Returns
329
- add_debug {"format_list => #{{'@list' => list}.inspect}"}
330
- {'@list' => list}
313
+ # Returns
314
+ ary = {'@list' => ary} unless predicate && list_range?(predicate)
315
+ debug {"format_list => #{ary.inspect}"}
316
+ ary
331
317
  end
332
318
 
333
319
  private
334
- ##
335
- # Generate @context
336
- # @return [Hash]
337
- def start_document
338
- ctx = new_hash
339
- ctx['@base'] = base_uri.to_s if base_uri
340
- ctx['@vocab'] = vocab.to_s if vocab
341
-
342
- # Prefixes
343
- prefixes.keys.sort {|a,b| a.to_s <=> b.to_s}.each do |k|
344
- next if DEFAULT_CONTEXT.has_key?(k.to_s)
345
- add_debug {"prefix[#{k}] => #{prefixes[k]}"}
346
- ctx[k.to_s] = prefixes[k].to_s
347
- end
348
-
349
- # Coerce
350
- add_debug {"start_doc: coerce= #{coerce.inspect}"}
351
- unless coerce == DEFAULT_COERCE
352
- c_h = new_hash
353
- coerce.keys.sort.each do |k|
354
- next if ['@type', RDF.type.to_s].include?(k.to_s)
355
- next if [DEFAULT_COERCE[k], false, RDF::XSD.integer.to_s, RDF::XSD.boolean.to_s].include?(coerce[k])
356
- k_iri = k == '@iri' ? '@iri' : format_uri(k, :position => :predicate)
357
- d_iri = format_uri(coerce[k], :position => :subject)
358
- add_debug {"coerce[#{k_iri}] => #{d_iri}, k=#{k.inspect}"}
359
- case c_h[d_iri]
360
- when nil
361
- c_h[d_iri] = k_iri
362
- when Array
363
- c_h[d_iri] << k_iri
364
- else
365
- c_h[d_iri] = [c_h[d_iri], k_iri]
366
- end
367
- end
368
-
369
- ctx['@coerce'] = c_h unless c_h.empty?
370
- end
371
-
372
- add_debug {"start_doc: context=#{ctx.inspect}"}
373
-
374
- # Return hash with @context, or empty
375
- r = new_hash
376
- r['@context'] = ctx unless ctx.empty?
377
- r
378
- end
379
-
380
320
  # Perform any preprocessing of statements required
381
321
  def preprocess
382
- # Load defined prefixes
383
- (@options[:prefixes] || {}).each_pair do |k, v|
384
- @iri_to_prefix[v.to_s] = k
385
- end
386
- @options[:prefixes] = new_hash # Will define actual used when matched
387
-
388
322
  @graph.each {|statement| preprocess_statement(statement)}
389
323
  end
390
324
 
391
325
  # Perform any statement preprocessing required. This is used to perform reference counts and determine required
392
326
  # prefixes.
327
+ #
393
328
  # @param [Statement] statement
394
329
  def preprocess_statement(statement)
395
- add_debug {"preprocess: #{statement.inspect}"}
330
+ debug {"preprocess: #{statement.inspect}"}
396
331
  references = ref_count(statement.object) + 1
397
332
  @references[statement.object] = references
398
333
  @subjects[statement.subject] = true
399
334
 
400
- # Pre-fetch qnames, to fill prefixes
401
- get_curie(statement.subject)
402
- get_curie(statement.predicate)
403
- if statement.object.literal?
404
- datatype_range?(statement.predicate) # To figure out coercion requirements
405
- else
406
- iri_range?(statement.predicate)
407
- get_curie(statement.object)
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)
408
349
  end
409
350
 
410
351
  @references[statement.predicate] = ref_count(statement.predicate) + 1
@@ -414,53 +355,53 @@ module JSON::LD
414
355
  # Option contains referencing property, if this is recursive
415
356
  # @return [Hash]
416
357
  def subject(subject, options = {})
417
- defn = new_hash
358
+ defn = Hash.new
418
359
 
419
360
  raise RDF::WriterError, "Illegal use of subject #{subject.inspect}, not supported" unless subject.resource?
420
361
 
421
362
  subject_done(subject)
422
363
  properties = @graph.properties(subject)
423
- add_debug {"subject: #{subject.inspect}, props: #{properties.inspect}"}
364
+ debug {"subject: #{subject.inspect}, props: #{properties.inspect}"}
424
365
 
425
366
  @graph.query(:subject => subject).each do |st|
426
367
  raise RDF::WriterError, "Illegal use of predicate #{st.predicate.inspect}, not supported in RDF/XML" unless st.predicate.uri?
427
368
  end
428
369
 
429
- if subject.node? && ref_count(subject) > (options[:property] ? 1 : 0) && options[:normalize]
370
+ if subject.node? && ref_count(subject) > (options[:property] ? 1 : 0) && options[:expand]
430
371
  raise RDF::WriterError, "Can't serialize named node when normalizing"
431
372
  end
432
373
 
433
374
  # Subject may be a list
434
375
  if is_valid_list?(subject)
435
- add_debug "subject is a list"
436
- defn['@subject'] = format_list(subject)
376
+ debug "subject is a list"
377
+ defn['@id'] = format_list(subject)
437
378
  properties.delete(RDF.first.to_s)
438
379
  properties.delete(RDF.rest.to_s)
439
380
 
440
381
  # Special case, if there are no properties, then we can just serialize the list itself
441
382
  return defn if properties.empty?
442
383
  elsif subject.uri? || ref_count(subject) > 1
443
- add_debug "subject is a uri"
384
+ debug "subject is an iri or it's a node referenced multiple times"
444
385
  # Don't need to set subject if it's a Node without references
445
- defn['@subject'] = format_uri(subject, :position => :subject)
386
+ defn['@id'] = format_iri(subject, :position => :subject)
446
387
  else
447
- add_debug "subject is an unreferenced BNode"
388
+ debug "subject is an unreferenced BNode"
448
389
  end
449
390
 
450
391
  prop_list = order_properties(properties)
451
- #add_debug {"=> property order: #{prop_list.to_sentence}"}
392
+ debug {"=> property order: #{prop_list.inspect}"}
452
393
 
453
394
  prop_list.each do |prop|
454
395
  predicate = RDF::URI.intern(prop)
455
396
 
456
- p_iri = format_uri(predicate, :position => :predicate)
457
- @depth += 1
458
- defn[p_iri] = property(predicate, properties[prop])
459
- add_debug {"prop(#{p_iri}) => #{properties[prop]} => #{defn[p_iri].inspect}"}
460
- @depth -= 1
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
461
402
  end
462
403
 
463
- add_debug {"subject: #{subject} has defn: #{defn.inspect}"}
404
+ debug {"subject: #{subject} has defn: #{defn.inspect}"}
464
405
  defn
465
406
  end
466
407
 
@@ -484,55 +425,13 @@ module JSON::LD
484
425
  if is_valid_list?(objects)
485
426
  format_list(objects, :property => predicate)
486
427
  elsif is_done?(objects) || !@subjects.include?(objects)
487
- format_uri(objects, :position => :object, :property => predicate)
428
+ format_iri(objects, :position => :object, :property => predicate)
488
429
  else
489
430
  subject(objects, :property => predicate)
490
431
  end
491
432
  end
492
433
  end
493
434
 
494
- ##
495
- # Return a CURIE for the IRI, or nil. Adds namespace of CURIE to defined prefixes
496
- # @param [RDF::Resource] resource
497
- # @return [String, nil] value to use to identify IRI
498
- def get_curie(resource)
499
- add_debug {"get_curie(#{resource.inspect})"}
500
- case resource
501
- when RDF::Node
502
- return resource.to_s
503
- when String
504
- iri = resource
505
- resource = RDF::URI(resource)
506
- return nil unless resource.absolute?
507
- when RDF::URI
508
- iri = resource.to_s
509
- return iri if options[:normalize]
510
- else
511
- return nil
512
- end
513
-
514
- curie = case
515
- when @iri_to_curie.has_key?(iri)
516
- return @iri_to_curie[iri]
517
- when u = @iri_to_prefix.keys.detect {|u| iri.index(u.to_s) == 0}
518
- # Use a defined prefix
519
- prefix = @iri_to_prefix[u]
520
- prefix(prefix, u) # Define for output
521
- iri.sub(u.to_s, "#{prefix}:")
522
- when @options[:standard_prefixes] && vocab = RDF::Vocabulary.detect {|v| iri.index(v.to_uri.to_s) == 0}
523
- prefix = vocab.__name__.to_s.split('::').last.downcase
524
- @iri_to_prefix[vocab.to_uri.to_s] = prefix
525
- prefix(prefix, vocab.to_uri) # Define for output
526
- iri.sub(vocab.to_uri.to_s, "#{prefix}:")
527
- else
528
- nil
529
- end
530
-
531
- @iri_to_curie[iri] = curie
532
- rescue Addressable::URI::InvalidURIError => e
533
- raise RDF::WriterError, "Invalid IRI #{resource.inspect}: #{e.message}"
534
- end
535
-
536
435
  ##
537
436
  # Take a hash from predicate IRIs to lists of values.
538
437
  # Sort the lists of values. Return a sorted list of properties.
@@ -542,8 +441,8 @@ module JSON::LD
542
441
  # Make sorted list of properties
543
442
  prop_list = []
544
443
 
545
- properties.keys.sort do |a,b|
546
- format_uri(a, :position => :predicate) <=> format_uri(b, :position => :predicate)
444
+ properties.keys.sort do |a, b|
445
+ format_iri(a, :position => :predicate) <=> format_iri(b, :position => :predicate)
547
446
  end.each do |prop|
548
447
  prop_list << prop.to_s
549
448
  end
@@ -553,7 +452,6 @@ module JSON::LD
553
452
 
554
453
  # Order subjects for output. Override this to output subjects in another order.
555
454
  #
556
- # Uses #base_uri.
557
455
  # @return [Array<Resource>] Ordered list of subjects
558
456
  def order_subjects
559
457
  seen = {}
@@ -561,14 +459,8 @@ module JSON::LD
561
459
 
562
460
  return @subjects.keys.sort do |a,b|
563
461
  format_iri(a, :position => :subject) <=> format_iri(b, :position => :subject)
564
- end if @options[:normalize]
462
+ end unless @options[:automatic]
565
463
 
566
- # Start with base_uri
567
- if base_uri && @subjects.keys.include?(base_uri)
568
- subjects << base_uri
569
- seen[base_uri] = true
570
- end
571
-
572
464
  # Sort subjects by resources over bnodes, ref_counts and the subject URI itself
573
465
  recursable = @subjects.keys.
574
466
  select {|s| !seen.include?(s)}.
@@ -589,16 +481,30 @@ module JSON::LD
589
481
  # @param [RDF::URI] predicate
590
482
  # @return [Boolean]
591
483
  def iri_range?(predicate)
592
- return false if predicate.nil? || @options[:normalize]
484
+ return false if predicate.nil? || [RDF.first, RDF.rest].include?(predicate) || @options[:expand]
485
+ return true if predicate == RDF.type
593
486
 
594
- unless coerce.has_key?(predicate.to_s)
595
- # objects of all statements with the predicate may not be literal
596
- coerce[predicate.to_s] = @graph.query(:predicate => predicate).to_a.any? {|st| st.object.literal?} ?
597
- false : '@iri'
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')
598
504
  end
599
505
 
600
- add_debug {"iri_range(#{predicate}) = #{coerce[predicate.to_s].inspect}"}
601
- coerce[predicate.to_s] == '@iri'
506
+ debug {"iri_range(#{predicate}) = #{context.coerce(predicate).inspect}"}
507
+ context.coerce(predicate) == '@id'
602
508
  end
603
509
 
604
510
  ##
@@ -606,23 +512,56 @@ module JSON::LD
606
512
  # @param [RDF::URI] predicate
607
513
  # @return [Boolean]
608
514
  def datatype_range?(predicate)
609
- unless coerce.has_key?(predicate.to_s)
515
+ unless context.coerce(predicate)
610
516
  # objects of all statements with the predicate must be literal
611
517
  # and have the same non-nil datatype
612
518
  dt = nil
613
- @graph.query(:predicate => predicate) do |st|
614
- if st.object.literal? && st.object.has_datatype?
615
- dt = st.object.datatype.to_s if dt.nil?
616
- dt = false unless dt == st.object.datatype.to_s
617
- else
618
- dt = false
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)
537
+ end
538
+
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)
619
555
  end
556
+ else
557
+ false
620
558
  end
621
- add_debug {"range(#{predicate}) = #{dt.inspect}"}
622
- coerce[predicate.to_s] = dt
559
+ context.list(predicate, true) if @list_range[predicate.to_s]
560
+
561
+ debug {"list(#{predicate}) = #{@list_range[predicate.to_s].inspect}"}
623
562
  end
624
563
 
625
- coerce[predicate.to_s]
564
+ @list_range[predicate.to_s]
626
565
  end
627
566
 
628
567
  # Reset internal helper instance variables
@@ -631,46 +570,13 @@ module JSON::LD
631
570
  @references = {}
632
571
  @serialized = {}
633
572
  @subjects = {}
634
- @iri_to_curie = {}
573
+ @list_range = {}
635
574
  end
636
575
 
637
- # Add debug event to debug array, if specified
638
- #
639
- # @param [String] message
640
- # @yieldreturn [String] appended to message, to allow for lazy-evaulation of message
641
- def add_debug(message = "")
642
- return unless ::JSON::LD.debug? || @options[:debug]
643
- message = message + yield if block_given?
644
- msg = "#{" " * @depth * 2}#{message}"
645
- STDERR.puts msg if ::JSON::LD::debug?
646
- @debug << msg if @debug.is_a?(Array)
647
- end
648
-
649
576
  # Checks if l is a valid RDF list, i.e. no nodes have other properties.
650
577
  def is_valid_list?(l)
651
- props = @graph.properties(l)
652
- unless l.node? && props.has_key?(RDF.first.to_s) || l == RDF.nil
653
- add_debug {"is_valid_list: false, #{l.inspect}: #{props.inspect}"}
654
- return false
655
- end
656
-
657
- while l && l != RDF.nil do
658
- #add_debug {"is_valid_list(length): #{props.length}"}
659
- return false unless props.has_key?(RDF.first.to_s) && props.has_key?(RDF.rest.to_s)
660
- n = props[RDF.rest.to_s]
661
- unless n.is_a?(Array) && n.length == 1
662
- add_debug {"is_valid_list: false, #{n.inspect}"}
663
- return false
664
- end
665
- l = n.first
666
- unless l.node? || l == RDF.nil
667
- add_debug {"is_valid_list: false, #{l.inspect}"}
668
- return false
669
- end
670
- props = @graph.properties(l)
671
- end
672
- add_debug {"is_valid_list: valid"}
673
- true
578
+ #debug {"is_valid_list: #{l.inspect}"}
579
+ return RDF::List.new(l, @graph).valid?
674
580
  end
675
581
 
676
582
  def is_done?(subject)
@@ -681,6 +587,26 @@ module JSON::LD
681
587
  def subject_done(subject)
682
588
  @serialized[subject] = true
683
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
609
+ end
684
610
  end
685
611
  end
686
612