json-ld 0.0.8 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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