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.
@@ -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