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.
@@ -0,0 +1,640 @@
1
+ require 'open-uri'
2
+ require 'json'
3
+ require 'bigdecimal'
4
+
5
+ module JSON::LD
6
+ class EvaluationContext # :nodoc:
7
+ # The base.
8
+ #
9
+ # The document base IRI, used for expanding relative IRIs.
10
+ #
11
+ # @attr_reader [RDF::URI]
12
+ attr_reader :base
13
+
14
+ # A list of current, in-scope mappings from term to IRI.
15
+ #
16
+ # @attr [Hash{String => String}]
17
+ attr :mappings, true
18
+
19
+ # Reverse mappings from IRI to a term or CURIE
20
+ #
21
+ # @attr [Hash{RDF::URI => String}]
22
+ attr :iri_to_curie, true
23
+
24
+ # Reverse mappings from IRI to term only for terms, not CURIEs
25
+ #
26
+ # @attr [Hash{RDF::URI => String}]
27
+ attr :iri_to_term, true
28
+
29
+ # Type coersion
30
+ #
31
+ # The @type keyword is used to specify type coersion rules for the data. For each key in the map, the
32
+ # key is a String representation of the property for which String values will be coerced and
33
+ # the value is the datatype (or @id) to coerce to. Type coersion for
34
+ # the value `@id` asserts that all vocabulary terms listed should undergo coercion to an IRI,
35
+ # including CURIE processing for compact IRI Expressions like `foaf:homepage`.
36
+ #
37
+ # @attr [Hash{String => String}]
38
+ attr :coercions, true
39
+
40
+ # List coercion
41
+ #
42
+ # The @list keyword is used to specify that properties having an array value are to be treated
43
+ # as an ordered list, rather than a normal unordered list
44
+ # @attr [Hash{String => true}]
45
+ attr :lists, true
46
+
47
+ # Default language
48
+ #
49
+ # This adds a language to plain strings that aren't otherwise coerced
50
+ # @attr [String]
51
+ attr :language, true
52
+
53
+ # Global options used in generating IRIs
54
+ # @attr [Hash] options
55
+ attr :options, true
56
+
57
+ # A context provided to us that we can use without re-serializing
58
+ attr :provided_context, true
59
+
60
+ ##
61
+ # Create new evaluation context
62
+ # @yield [ec]
63
+ # @yieldparam [EvaluationContext]
64
+ # @return [EvaluationContext]
65
+ def initialize(options = {})
66
+ @base = RDF::URI(options[:base_uri]) if options[:base_uri]
67
+ @mappings = {}
68
+ @coercions = {}
69
+ @lists = {}
70
+ @iri_to_curie = {}
71
+ @iri_to_term = {
72
+ RDF.to_uri.to_s => "rdf",
73
+ RDF::XSD.to_uri.to_s => "xsd"
74
+ }
75
+
76
+ @options = options
77
+
78
+ # Load any defined prefixes
79
+ (options[:prefixes] || {}).each_pair do |k, v|
80
+ @iri_to_term[v.to_s] = k
81
+ end
82
+
83
+ debug("init") {"iri_to_term: #{iri_to_term.inspect}"}
84
+
85
+ yield(self) if block_given?
86
+ end
87
+
88
+ # Create an Evaluation Context using an existing context as a start by parsing the input.
89
+ #
90
+ # @param [IO, Array, Hash, String] input
91
+ # @return [EvaluationContext] context
92
+ # @raise [InvalidContext]
93
+ # on a remote context load error, syntax error, or a reference to a term which is not defined.
94
+ def parse(context)
95
+ case context
96
+ when EvaluationContext
97
+ debug("parse") {"context: #{context.inspect}"}
98
+ context.dup
99
+ when IO, StringIO
100
+ debug("parse") {"io: #{context}"}
101
+ # Load context document, if it is a string
102
+ begin
103
+ ctx = JSON.load(context)
104
+ raise JSON::LD::InvalidContext::Syntax, "missing @context" unless ctx.is_a?(Hash) && ctx["@context"]
105
+ parse(ctx["@context"])
106
+ rescue JSON::ParserError => e
107
+ debug("parse") {"Failed to parse @context from remote document at #{context}: #{e.message}"}
108
+ raise JSON::LD::InvalidContext::Syntax, "Failed to parse remote context at #{context}: #{e.message}" if @options[:validate]
109
+ self.dup
110
+ end
111
+ when String, nil
112
+ debug("parse") {"remote: #{context}"}
113
+ # Load context document, if it is a string
114
+ ec = nil
115
+ begin
116
+ open(context.to_s) {|f| ec = parse(f)}
117
+ ec.provided_context = context
118
+ debug("parse") {"=> provided_context: #{context.inspect}"}
119
+ ec
120
+ rescue Exception => e
121
+ debug("parse") {"Failed to retrieve @context from remote document at #{context}: #{e.message}"}
122
+ raise JSON::LD::InvalidContext::LoadError, "Failed to parse remote context at #{context}: #{e.message}", e.backtrace if @options[:validate]
123
+ self.dup
124
+ end
125
+ when Array
126
+ # Process each member of the array in order, updating the active context
127
+ # Updates evaluation context serially during parsing
128
+ debug("parse") {"Array"}
129
+ ec = self
130
+ context.each {|c| ec = ec.parse(c)}
131
+ ec.provided_context = context
132
+ debug("parse") {"=> provided_context: #{context.inspect}"}
133
+ ec
134
+ when Hash
135
+ new_ec = self.dup
136
+ new_ec.provided_context = context
137
+ debug("parse") {"=> provided_context: #{context.inspect}"}
138
+
139
+ num_updates = 1
140
+ while num_updates > 0 do
141
+ num_updates = 0
142
+
143
+ # Map terms to IRIs first
144
+ context.each do |key, value|
145
+ # Expand a string value, unless it matches a keyword
146
+ debug("parse") {"Hash[#{key}] = #{value.inspect}"}
147
+ if (new_ec.mapping(key) || key) == '@language'
148
+ new_ec.language = value.to_s
149
+ elsif term_valid?(key)
150
+ # Extract IRI mapping. This is complicated, as @id may have been aliased
151
+ if value.is_a?(Hash)
152
+ id_key = value.keys.detect {|k| new_ec.mapping(k) == '@id'} || '@id'
153
+ value = value[id_key]
154
+ end
155
+ raise InvalidContext::Syntax, "unknown mapping for #{key.inspect} to #{value.class}" unless value.is_a?(String) || value.nil?
156
+
157
+ iri = new_ec.expand_iri(value, :position => :predicate) if value.is_a?(String)
158
+ if iri && new_ec.mappings[key] != iri
159
+ # Record term definition
160
+ new_ec.mapping(key, iri)
161
+ num_updates += 1
162
+ end
163
+ elsif !new_ec.expand_iri(key).is_a?(RDF::URI)
164
+ raise InvalidContext::Syntax, "key #{key.inspect} is invalid"
165
+ end
166
+ end
167
+ end
168
+
169
+ # Next, look for coercion using new_ec
170
+ context.each do |key, value|
171
+ # Expand a string value, unless it matches a keyword
172
+ debug("parse") {"coercion/list: Hash[#{key}] = #{value.inspect}"}
173
+ prop = new_ec.expand_iri(key, :position => :predicate).to_s
174
+ case value
175
+ when Hash
176
+ # Must have one of @id, @type or @list
177
+ expanded_keys = value.keys.map {|k| new_ec.mapping(k) || k}
178
+ raise InvalidContext::Syntax, "mapping for #{key.inspect} missing one of @id, @type or @list" if (%w(@id @type @list) & expanded_keys).empty?
179
+ raise InvalidContext::Syntax, "unknown mappings for #{key.inspect}: #{value.keys.inspect}" unless (expanded_keys - %w(@id @type @list)).empty?
180
+ value.each do |key2, value2|
181
+ expanded_key = new_ec.mapping(key2) || key2
182
+ iri = new_ec.expand_iri(value2, :position => :predicate) if value2.is_a?(String)
183
+ case expanded_key
184
+ when '@type'
185
+ raise InvalidContext::Syntax, "unknown mapping for '@type' to #{value2.class}" unless value2.is_a?(String) || value2.nil?
186
+ if new_ec.coerce(prop) != iri
187
+ raise InvalidContext::Syntax, "unknown mapping for '@type' to #{iri.inspect}" unless RDF::URI(iri).absolute? || iri == '@id'
188
+ # Record term coercion
189
+ debug("parse") {"coerce #{prop.inspect} to #{iri.inspect}"}
190
+ new_ec.coerce(prop, iri)
191
+ end
192
+ when '@list'
193
+ raise InvalidContext::Syntax, "unknown mapping for '@list' to #{value2.class}" unless value2.is_a?(TrueClass) || value2.is_a?(FalseClass)
194
+ if new_ec.list(prop) != value2
195
+ debug("parse") {"list #{prop.inspect} as #{value2.inspect}"}
196
+ new_ec.list(prop, value2)
197
+ end
198
+ end
199
+ end
200
+ when String
201
+ # handled in previous loop
202
+ else
203
+ raise InvalidContext::Syntax, "attemp to map #{key.inspect} to #{value.class}"
204
+ end
205
+ end
206
+
207
+ new_ec
208
+ end
209
+ end
210
+
211
+ ##
212
+ # Generate @context
213
+ #
214
+ # If a context was supplied in global options, use that, otherwise, generate one
215
+ # from this representation.
216
+ #
217
+ # @param [Hash{Symbol => Object}] options ({})
218
+ # @return [Hash]
219
+ def serialize(options = {})
220
+ depth(options) do
221
+ use_context = if provided_context
222
+ debug "serlialize: reuse context: #{provided_context.inspect}"
223
+ provided_context
224
+ else
225
+ debug("serlialize: generate context")
226
+ debug {"=> context: #{inspect}"}
227
+ ctx = Hash.new
228
+ ctx['@language'] = language.to_s if language
229
+
230
+ # Prefixes
231
+ mappings.keys.sort {|a,b| a.to_s <=> b.to_s}.each do |k|
232
+ next unless term_valid?(k.to_s)
233
+ debug {"=> mappings[#{k}] => #{mappings[k]}"}
234
+ ctx[k.to_s] = mappings[k].to_s
235
+ end
236
+
237
+ unless coercions.empty? && lists.empty?
238
+ # Coerce
239
+ (coercions.keys + lists.keys).uniq.sort.each do |k|
240
+ next if ['@type', RDF.type.to_s].include?(k.to_s)
241
+
242
+ k_iri = compact_iri(k, :position => :predicate, :depth => @depth).to_s
243
+ k_prefix = k_iri.split(':').first
244
+
245
+ # Turn into long form
246
+ ctx[k_iri] ||= Hash.new
247
+ if ctx[k_iri].is_a?(String)
248
+ defn = Hash.new
249
+ defn[self.alias("@id")] = ctx[k_iri]
250
+ ctx[k_iri] = defn
251
+ end
252
+
253
+ debug {"=> coerce(#{k}) => #{coerce(k)}"}
254
+ if coerce(k) && !NATIVE_DATATYPES.include?(coerce(k))
255
+ # If coercion doesn't depend on any prefix definitions, it can be folded into the first context block
256
+ dt = compact_iri(coerce(k), :position => :datatype, :depth => @depth)
257
+ # Fold into existing definition
258
+ ctx[k_iri][self.alias("@type")] = dt
259
+ debug {"=> reuse datatype[#{k_iri}] => #{dt}"}
260
+ end
261
+
262
+ debug {"=> list(#{k}) => #{list(k)}"}
263
+ if list(k)
264
+ # It is not dependent on previously defined terms, fold into existing definition
265
+ ctx[k_iri][self.alias("@list")] = true
266
+ debug {"=> reuse list_range[#{k_iri}] => true"}
267
+ end
268
+
269
+ # Remove an empty definition
270
+ ctx.delete(k_iri) if ctx[k_iri].empty?
271
+ end
272
+ end
273
+
274
+ debug {"start_doc: context=#{ctx.inspect}"}
275
+ ctx
276
+ end
277
+
278
+ # Return hash with @context, or empty
279
+ r = Hash.new
280
+ r['@context'] = use_context unless use_context.nil? || use_context.empty?
281
+ r
282
+ end
283
+ end
284
+
285
+ ##
286
+ # Retrieve term mapping, add it if `value` is provided
287
+ #
288
+ # @param [String, #to_s] term
289
+ # @param [RDF::URI, String] value (nil)
290
+ #
291
+ # @return [RDF::URI, String]
292
+ def mapping(term, value = nil)
293
+ if value
294
+ debug {"map #{term.inspect} to #{value}"} unless @mappings[term.to_s] == value
295
+ @mappings[term.to_s] = value
296
+ iri_to_term[value.to_s] = term
297
+ end
298
+ @mappings.has_key?(term.to_s) && @mappings[term.to_s]
299
+ end
300
+
301
+ ##
302
+ # Revered term mapping, typically used for finding aliases for keys.
303
+ #
304
+ # Returns either the original value, or a mapping for this value.
305
+ #
306
+ # @example
307
+ # {"@context": {"id": "@id"}, "@id": "foo"} => {"id": "foo"}
308
+ #
309
+ # @param [RDF::URI, String] value
310
+ # @return [RDF::URI, String]
311
+ def alias(value)
312
+ @mappings.invert.fetch(value, value)
313
+ end
314
+
315
+ ##
316
+ # Retrieve term coercion, add it if `value` is provided
317
+ #
318
+ # @param [String] property in full IRI string representation
319
+ # @param [RDF::URI, '@id'] value (nil)
320
+ #
321
+ # @return [RDF::URI, '@id']
322
+ def coerce(property, value = nil)
323
+ # Map property, if it's not an RDF::Value
324
+ debug("coerce") {"map #{property} to #{mapping(property)}"} if mapping(property)
325
+ property = mapping(property) if mapping(property)
326
+ return '@id' if [RDF.type, '@type'].include?(property) # '@type' always is an IRI
327
+ if value
328
+ debug {"coerce #{property.inspect} to #{value}"} unless @coercions[property.to_s] == value
329
+ @coercions[property.to_s] = value
330
+ end
331
+ @coercions[property.to_s] if @coercions.has_key?(property.to_s)
332
+ end
333
+
334
+ ##
335
+ # Retrieve list mapping, add it if `value` is provided
336
+ #
337
+ # @param [String] property in full IRI string representation
338
+ # @param [Boolean] value (nil)
339
+ # @return [Boolean]
340
+ def list(property, value = nil)
341
+ unless value.nil?
342
+ debug {"coerce #{property.inspect} to @list"} unless @lists[property.to_s] == value
343
+ @lists[property.to_s] = value
344
+ end
345
+ @lists[property.to_s] && @lists[property.to_s]
346
+ end
347
+
348
+ ##
349
+ # Determine if `term` is a suitable term
350
+ #
351
+ # @param [String] term
352
+ # @return [Boolean]
353
+ def term_valid?(term)
354
+ term.empty? || term.match(NC_REGEXP)
355
+ end
356
+
357
+ ##
358
+ # Expand an IRI. Relative IRIs are expanded against any document base.
359
+ #
360
+ # @param [String] iri
361
+ # A keyword, term, prefix:suffix or possibly relative IRI
362
+ # @param [Hash{Symbol => Object}] options
363
+ # @option options [:subject, :predicate, :object, :datatype] position
364
+ # Useful when determining how to serialize.
365
+ #
366
+ # @return [RDF::URI, String] IRI or String, if it's a keyword
367
+ # @raise [RDF::ReaderError] if the iri cannot be expanded
368
+ # @see http://json-ld.org/spec/latest/json-ld-api/#iri-expansion
369
+ def expand_iri(iri, options = {})
370
+ return iri unless iri.is_a?(String)
371
+ prefix, suffix = iri.split(":", 2)
372
+ debug("expand_iri") {"prefix: #{prefix.inspect}, suffix: #{suffix.inspect}"}
373
+ prefix = prefix.to_s
374
+ case
375
+ when prefix == '_' then bnode(suffix)
376
+ when iri.to_s[0,1] == "@" then iri
377
+ when mappings.has_key?(prefix) then uri(mappings[prefix] + suffix.to_s)
378
+ when base then base.join(iri)
379
+ else uri(iri)
380
+ end
381
+ end
382
+
383
+ ##
384
+ # Compact an IRI
385
+ #
386
+ # @param [RDF::URI] iri
387
+ # @param [Hash{Symbol => Object}] options ({})
388
+ # @option options [:subject, :predicate, :object, :datatype] position
389
+ # Useful when determining how to serialize.
390
+ #
391
+ # @return [String] compacted form of IRI
392
+ # @see http://json-ld.org/spec/latest/json-ld-api/#iri-compaction
393
+ def compact_iri(iri, options = {})
394
+ return iri.to_s if [RDF.first, RDF.rest, RDF.nil].include?(iri) # Don't cause these to be compacted
395
+
396
+ depth(options) do
397
+ debug {"compact_iri(#{options.inspect}, #{iri.inspect})"}
398
+
399
+ result = self.alias('@type') if options[:position] == :predicate && iri == RDF.type
400
+ result ||= get_curie(iri) || self.alias(iri.to_s)
401
+
402
+ debug {"=> #{result.inspect}"}
403
+ result
404
+ end
405
+ end
406
+
407
+ ##
408
+ # Expand a value from compacted to expanded form making the context
409
+ # unnecessary. This method is used as part of more general expansion
410
+ # and operates on RHS values, using a supplied key to determine @type and @list
411
+ # coercion rules.
412
+ #
413
+ # @param [RDF::URI] predicate
414
+ # Associated predicate used to find coercion rules
415
+ # @param [Hash, String] value
416
+ # Value (literal or IRI) to be expanded
417
+ # @param [Hash{Symbol => Object}] options
418
+ #
419
+ # @return [Hash] Object representation of value
420
+ # @raise [RDF::ReaderError] if the iri cannot be expanded
421
+ # @see http://json-ld.org/spec/latest/json-ld-api/#value-expansion
422
+ def expand_value(predicate, value, options = {})
423
+ depth(options) do
424
+ debug("expand_value") {"predicate: #{predicate}, value: #{value.inspect}, coerce: #{coerce(predicate).inspect}"}
425
+ result = case value
426
+ when TrueClass, FalseClass, RDF::Literal::Boolean
427
+ {"@literal" => value.to_s, "@type" => RDF::XSD.boolean.to_s}
428
+ when Integer, RDF::Literal::Integer
429
+ {"@literal" => value.to_s, "@type" => RDF::XSD.integer.to_s}
430
+ when BigDecimal, RDF::Literal::Decimal
431
+ {"@literal" => value.to_s, "@type" => RDF::XSD.decimal.to_s}
432
+ when Float, RDF::Literal::Double
433
+ {"@literal" => value.to_s, "@type" => RDF::XSD.double.to_s}
434
+ when Date, Time, DateTime
435
+ l = RDF::Literal(value)
436
+ {"@literal" => l.to_s, "@type" => l.datatype.to_s}
437
+ when RDF::URI
438
+ {'@id' => value.to_s}
439
+ when RDF::Literal
440
+ res = Hash.new
441
+ res['@literal'] = value.to_s
442
+ res['@type'] = value.datatype.to_s if value.has_datatype?
443
+ res['@language'] = value.language.to_s if value.has_language?
444
+ res
445
+ else
446
+ case coerce(predicate)
447
+ when '@id'
448
+ {'@id' => expand_iri(value, :position => :object).to_s}
449
+ when nil
450
+ language ? {"@literal" => value.to_s, "@language" => language.to_s} : value.to_s
451
+ else
452
+ res = Hash.new
453
+ res['@literal'] = value.to_s
454
+ res['@type'] = coerce(predicate).to_s
455
+ res
456
+ end
457
+ end
458
+
459
+ debug {"=> #{result.inspect}"}
460
+ result
461
+ end
462
+ end
463
+
464
+ ##
465
+ # Compact a value
466
+ #
467
+ # @param [RDF::URI] predicate
468
+ # Associated predicate used to find coercion rules
469
+ # @param [Hash] value
470
+ # Value (literal or IRI), in full object representation, to be compacted
471
+ # @param [Hash{Symbol => Object}] options
472
+ #
473
+ # @return [Hash] Object representation of value
474
+ # @raise [ProcessingError] if the iri cannot be expanded
475
+ # @see http://json-ld.org/spec/latest/json-ld-api/#value-compaction
476
+ def compact_value(predicate, value, options = {})
477
+ raise ProcessingError::Lossy, "attempt to compact a non-object value" unless value.is_a?(Hash)
478
+
479
+ depth(options) do
480
+ debug("compact_value") {"predicate: #{predicate.inspect}, value: #{value.inspect}, coerce: #{coerce(predicate).inspect}"}
481
+
482
+ result = case
483
+ when %w(boolean integer double).any? {|t| expand_iri(value['@type'], :position => :datatype) == RDF::XSD[t]}
484
+ # Compact native type
485
+ debug {" (native)"}
486
+ l = RDF::Literal(value['@literal'], :datatype => expand_iri(value['@type'], :position => :datatype))
487
+ l.canonicalize.object
488
+ when coerce(predicate) == '@id' && value.has_key?('@id')
489
+ # Compact an @id coercion
490
+ debug {" (@id & coerce)"}
491
+ compact_iri(value['@id'], :position => :object)
492
+ when value['@type'] && expand_iri(value['@type'], :position => :datatype) == coerce(predicate)
493
+ # Compact common datatype
494
+ debug {" (@type & coerce) == #{coerce(predicate)}"}
495
+ value['@literal']
496
+ when value.has_key?('@id')
497
+ # Compact an IRI
498
+ value['@id'] = compact_iri(value['@id'], :position => :object)
499
+ debug {" (@id => #{value['@id']})"}
500
+ value
501
+ when value['@language'] && value['@language'] == language
502
+ # Compact language
503
+ debug {" (@language) == #{language}"}
504
+ value['@literal']
505
+ when value['@literal'] && !value['@language'] && !value['@type'] && !coerce(predicate) && !language
506
+ # Compact simple literal to string
507
+ debug {" (@literal && !@language && !@type && !coerce && !language)"}
508
+ value['@literal']
509
+ when value['@type']
510
+ # Compact datatype
511
+ debug {" (@type)"}
512
+ value['@type'] = compact_iri(value['@type'], :position => :datatype)
513
+ value
514
+ else
515
+ # Otherwise, use original value
516
+ debug {" (no change)"}
517
+ value
518
+ end
519
+
520
+ # If the result is an object, tranform keys using any term keyword aliases
521
+ if result.is_a?(Hash) && result.keys.any? {|k| self.alias(k) != k}
522
+ debug {" (map to key aliases)"}
523
+ new_element = {}
524
+ result.each do |k, v|
525
+ new_element[self.alias(k)] = v
526
+ end
527
+ result = new_element
528
+ end
529
+
530
+ debug {"=> #{result.inspect}"}
531
+ result
532
+ end
533
+ end
534
+
535
+ def inspect
536
+ v = %w([EvaluationContext)
537
+ v << "mappings[#{mappings.keys.length}]=#{mappings}"
538
+ v << "coercions[#{coercions.keys.length}]=#{coercions}"
539
+ v << "lists[#{lists.length}]=#{lists}"
540
+ v.join(", ") + "]"
541
+ end
542
+
543
+ def dup
544
+ # Also duplicate mappings, coerce and list
545
+ ec = super
546
+ ec.mappings = mappings.dup
547
+ ec.coercions = coercions.dup
548
+ ec.lists = lists.dup
549
+ ec.language = language
550
+ ec.options = options
551
+ ec.iri_to_term = iri_to_term.dup
552
+ ec.iri_to_curie = iri_to_curie.dup
553
+ ec
554
+ end
555
+
556
+ private
557
+
558
+ def uri(value, append = nil)
559
+ value = RDF::URI.new(value)
560
+ value = value.join(append) if append
561
+ value.validate! if @options[:validate]
562
+ value.canonicalize! if @options[:canonicalize]
563
+ value = RDF::URI.intern(value) if @options[:intern]
564
+ value
565
+ end
566
+
567
+ # Keep track of allocated BNodes
568
+ #
569
+ # Don't actually use the name provided, to prevent name alias issues.
570
+ # @return [RDF::Node]
571
+ def bnode(value = nil)
572
+ @@bnode_cache ||= {}
573
+ @@bnode_cache[value.to_s] ||= RDF::Node.new(value)
574
+ end
575
+
576
+ ##
577
+ # Return a CURIE for the IRI, or nil. Adds namespace of CURIE to defined prefixes
578
+ # @param [RDF::Resource] resource
579
+ # @return [String, nil] value to use to identify IRI
580
+ def get_curie(resource)
581
+ debug {"get_curie(#{resource.inspect})"}
582
+ case resource
583
+ when RDF::Node, /^_:/
584
+ return resource.to_s
585
+ when String
586
+ iri = resource
587
+ resource = RDF::URI(resource)
588
+ return nil unless resource.absolute?
589
+ when RDF::URI
590
+ iri = resource.to_s
591
+ return iri if options[:expand]
592
+ else
593
+ return nil
594
+ end
595
+
596
+ curie = case
597
+ when iri_to_curie.has_key?(iri)
598
+ return iri_to_curie[iri]
599
+ when u = iri_to_term.keys.detect {|i| iri.index(i.to_s) == 0}
600
+ # Use a defined prefix
601
+ prefix = iri_to_term[u]
602
+ mapping(prefix, u)
603
+ iri.sub(u.to_s, "#{prefix}:").sub(/:$/, '')
604
+ when @options[:standard_prefixes] && vocab = RDF::Vocabulary.detect {|v| iri.index(v.to_uri.to_s) == 0}
605
+ prefix = vocab.__name__.to_s.split('::').last.downcase
606
+ mapping(prefix, vocab.to_uri.to_s)
607
+ iri.sub(vocab.to_uri.to_s, "#{prefix}:").sub(/:$/, '')
608
+ else
609
+ debug "no mapping found for #{iri} in #{iri_to_term.inspect}"
610
+ nil
611
+ end
612
+
613
+ iri_to_curie[iri] = curie
614
+ rescue Addressable::URI::InvalidURIError => e
615
+ raise RDF::WriterError, "Invalid IRI #{resource.inspect}: #{e.message}"
616
+ end
617
+
618
+ # Add debug event to debug array, if specified
619
+ #
620
+ # @param [String] message
621
+ # @yieldreturn [String] appended to message, to allow for lazy-evaulation of message
622
+ def debug(*args)
623
+ return unless ::JSON::LD.debug? || @options[:debug]
624
+ list = args
625
+ list << yield if block_given?
626
+ message = " " * (@depth || 0) * 2 + (list.empty? ? "" : list.join(": "))
627
+ puts message if JSON::LD::debug?
628
+ @options[:debug] << message if @options[:debug].is_a?(Array)
629
+ end
630
+
631
+ # Increase depth around a method invocation
632
+ def depth(options = {})
633
+ old_depth = @depth || 0
634
+ @depth = (options[:depth] || old_depth) + 1
635
+ ret = yield
636
+ @depth = old_depth
637
+ ret
638
+ end
639
+ end
640
+ end