json-ld 3.1.2 → 3.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +124 -48
- data/VERSION +1 -1
- data/bin/jsonld +27 -30
- data/lib/json/ld.rb +6 -2
- data/lib/json/ld/api.rb +33 -24
- data/lib/json/ld/compact.rb +65 -37
- data/lib/json/ld/conneg.rb +1 -1
- data/lib/json/ld/context.rb +612 -539
- data/lib/json/ld/expand.rb +158 -77
- data/lib/json/ld/format.rb +20 -7
- data/lib/json/ld/from_rdf.rb +40 -17
- data/lib/json/ld/reader.rb +20 -11
- data/lib/json/ld/streaming_reader.rb +578 -0
- data/lib/json/ld/to_rdf.rb +9 -5
- data/lib/json/ld/writer.rb +10 -3
- data/spec/compact_spec.rb +207 -2
- data/spec/context_spec.rb +13 -60
- data/spec/expand_spec.rb +248 -0
- data/spec/from_rdf_spec.rb +181 -0
- data/spec/matchers.rb +1 -1
- data/spec/reader_spec.rb +33 -34
- data/spec/streaming_reader_spec.rb +237 -0
- data/spec/suite_helper.rb +14 -8
- data/spec/suite_to_rdf_spec.rb +1 -0
- data/spec/to_rdf_spec.rb +206 -0
- data/spec/writer_spec.rb +193 -0
- metadata +9 -6
data/lib/json/ld/conneg.rb
CHANGED
@@ -46,7 +46,7 @@ module JSON::LD
|
|
46
46
|
#
|
47
47
|
# @param [Hash{String => String}] env
|
48
48
|
# @return [Array(Integer, Hash, #each)] Status, Headers and Body
|
49
|
-
# @see
|
49
|
+
# @see https://rubydoc.info/github/rack/rack/file/SPEC
|
50
50
|
def call(env)
|
51
51
|
response = app.call(env)
|
52
52
|
body = response[2].respond_to?(:body) ? response[2].body : response[2]
|
data/lib/json/ld/context.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
require 'json'
|
4
4
|
require 'bigdecimal'
|
5
5
|
require 'set'
|
6
|
+
require 'rdf/util/cache'
|
7
|
+
|
6
8
|
begin
|
7
9
|
# Attempt to load this to avoid unnecessary context fetches
|
8
10
|
require 'json-ld-preloaded'
|
@@ -21,6 +23,14 @@ module JSON::LD
|
|
21
23
|
# @return [Hash{Symbol => Context}]
|
22
24
|
PRELOADED = {}
|
23
25
|
|
26
|
+
# Initial contexts, defined on first access
|
27
|
+
INITIAL_CONTEXTS = {}
|
28
|
+
|
29
|
+
##
|
30
|
+
# Defines the maximum number of interned URI references that can be held
|
31
|
+
# cached in memory at any one time.
|
32
|
+
CACHE_SIZE = 100 # unlimited by default
|
33
|
+
|
24
34
|
class << self
|
25
35
|
##
|
26
36
|
# Add preloaded context. In the block form, the context is lazy evaulated on first use.
|
@@ -40,242 +50,11 @@ module JSON::LD
|
|
40
50
|
end
|
41
51
|
end
|
42
52
|
|
43
|
-
# Term Definitions specify how properties and values have to be interpreted as well as the current vocabulary mapping and the default language
|
44
|
-
class TermDefinition
|
45
|
-
# @return [RDF::URI] IRI map
|
46
|
-
attr_accessor :id
|
47
|
-
|
48
|
-
# @return [String] term name
|
49
|
-
attr_accessor :term
|
50
|
-
|
51
|
-
# @return [String] Type mapping
|
52
|
-
attr_accessor :type_mapping
|
53
|
-
|
54
|
-
# Base container mapping, without @set
|
55
|
-
# @return [Array<'@index', '@language', '@index', '@set', '@type', '@id', '@graph'>] Container mapping
|
56
|
-
attr_reader :container_mapping
|
57
|
-
|
58
|
-
# @return [String] Term used for nest properties
|
59
|
-
attr_accessor :nest
|
60
|
-
|
61
|
-
# Language mapping of term, `false` is used if there is an explicit language mapping for this term.
|
62
|
-
# @return [String] Language mapping
|
63
|
-
attr_accessor :language_mapping
|
64
|
-
|
65
|
-
# Direction of term, `false` is used if there is explicit direction mapping mapping for this term.
|
66
|
-
# @return ["ltr", "rtl"] direction_mapping
|
67
|
-
attr_accessor :direction_mapping
|
68
|
-
|
69
|
-
# @return [Boolean] Reverse Property
|
70
|
-
attr_accessor :reverse_property
|
71
|
-
|
72
|
-
# This is a simple term definition, not an expanded term definition
|
73
|
-
# @return [Boolean]
|
74
|
-
attr_accessor :simple
|
75
|
-
|
76
|
-
# Property used for data indexing; defaults to @index
|
77
|
-
# @return [Boolean]
|
78
|
-
attr_accessor :index
|
79
|
-
|
80
|
-
# Indicate that term may be used as a prefix
|
81
|
-
attr_writer :prefix
|
82
|
-
|
83
|
-
# Term-specific context
|
84
|
-
# @return [Hash{String => Object}]
|
85
|
-
attr_accessor :context
|
86
|
-
|
87
|
-
# Term is protected.
|
88
|
-
# @return [Boolean]
|
89
|
-
attr_writer :protected
|
90
|
-
|
91
|
-
# This is a simple term definition, not an expanded term definition
|
92
|
-
# @return [Boolean] simple
|
93
|
-
def simple?; simple; end
|
94
|
-
|
95
|
-
# This is an appropriate term to use as the prefix of a compact IRI
|
96
|
-
# @return [Boolean] simple
|
97
|
-
def prefix?; @prefix; end
|
98
|
-
|
99
|
-
# Create a new Term Mapping with an ID
|
100
|
-
# @param [String] term
|
101
|
-
# @param [String] id
|
102
|
-
# @param [String] type_mapping Type mapping
|
103
|
-
# @param [Set<'@index', '@language', '@index', '@set', '@type', '@id', '@graph'>] container_mapping
|
104
|
-
# @param [String] language_mapping
|
105
|
-
# Language mapping of term, `false` is used if there is an explicit language mapping for this term
|
106
|
-
# @param ["ltr", "rtl"] direction_mapping
|
107
|
-
# Direction mapping of term, `false` is used if there is an explicit direction mapping for this term
|
108
|
-
# @param [Boolean] reverse_property
|
109
|
-
# @param [Boolean] protected
|
110
|
-
# @param [String] nest term used for nest properties
|
111
|
-
# @param [Boolean] simple
|
112
|
-
# This is a simple term definition, not an expanded term definition
|
113
|
-
# @param [Boolean] prefix
|
114
|
-
# Term may be used as a prefix
|
115
|
-
def initialize(term,
|
116
|
-
id: nil,
|
117
|
-
index: nil,
|
118
|
-
type_mapping: nil,
|
119
|
-
container_mapping: nil,
|
120
|
-
language_mapping: nil,
|
121
|
-
direction_mapping: nil,
|
122
|
-
reverse_property: false,
|
123
|
-
nest: nil,
|
124
|
-
protected: false,
|
125
|
-
simple: false,
|
126
|
-
prefix: nil,
|
127
|
-
context: nil)
|
128
|
-
@term = term
|
129
|
-
@id = id.to_s unless id.nil?
|
130
|
-
@index = index.to_s unless index.nil?
|
131
|
-
@type_mapping = type_mapping.to_s unless type_mapping.nil?
|
132
|
-
self.container_mapping = container_mapping
|
133
|
-
@language_mapping = language_mapping unless language_mapping.nil?
|
134
|
-
@direction_mapping = direction_mapping unless direction_mapping.nil?
|
135
|
-
@reverse_property = reverse_property
|
136
|
-
@protected = protected
|
137
|
-
@nest = nest unless nest.nil?
|
138
|
-
@simple = simple
|
139
|
-
@prefix = prefix unless prefix.nil?
|
140
|
-
@context = context unless context.nil?
|
141
|
-
end
|
142
|
-
|
143
|
-
# Term is protected.
|
144
|
-
# @return [Boolean]
|
145
|
-
def protected?; !!@protected; end
|
146
|
-
|
147
|
-
# Set container mapping, from an array which may include @set
|
148
|
-
def container_mapping=(mapping)
|
149
|
-
mapping = case mapping
|
150
|
-
when Set then mapping
|
151
|
-
when Array then Set.new(mapping)
|
152
|
-
when String then Set[mapping]
|
153
|
-
when nil then Set.new
|
154
|
-
else
|
155
|
-
raise "Shouldn't happen with #{mapping.inspect}"
|
156
|
-
end
|
157
|
-
if @as_set = mapping.include?('@set')
|
158
|
-
mapping = mapping.dup
|
159
|
-
mapping.delete('@set')
|
160
|
-
end
|
161
|
-
@container_mapping = mapping
|
162
|
-
@index ||= '@index' if mapping.include?('@index')
|
163
|
-
end
|
164
|
-
|
165
|
-
##
|
166
|
-
# Output Hash or String definition for this definition considering @language and @vocab
|
167
|
-
#
|
168
|
-
# @param [Context] context
|
169
|
-
# @return [String, Hash{String => Array[String], String}]
|
170
|
-
def to_context_definition(context)
|
171
|
-
cid = if context.vocab && id.start_with?(context.vocab)
|
172
|
-
# Nothing to return unless it's the same as the vocab
|
173
|
-
id == context.vocab ? context.vocab : id.to_s[context.vocab.length..-1]
|
174
|
-
else
|
175
|
-
# Find a term to act as a prefix
|
176
|
-
iri, prefix = context.iri_to_term.detect {|i,p| id.to_s.start_with?(i.to_s)}
|
177
|
-
iri && iri != id ? "#{prefix}:#{id.to_s[iri.length..-1]}" : id
|
178
|
-
end
|
179
|
-
|
180
|
-
if simple?
|
181
|
-
cid.to_s unless cid == term && context.vocab
|
182
|
-
else
|
183
|
-
defn = {}
|
184
|
-
defn[reverse_property ? '@reverse' : '@id'] = cid.to_s unless cid == term && !reverse_property
|
185
|
-
if type_mapping
|
186
|
-
defn['@type'] = if KEYWORDS.include?(type_mapping)
|
187
|
-
type_mapping
|
188
|
-
else
|
189
|
-
context.compact_iri(type_mapping, vocab: true)
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
cm = Array(container_mapping)
|
194
|
-
cm << "@set" if as_set? && !cm.include?("@set")
|
195
|
-
cm = cm.first if cm.length == 1
|
196
|
-
defn['@container'] = cm unless cm.empty?
|
197
|
-
# Language set as false to be output as null
|
198
|
-
defn['@language'] = (@language_mapping ? @language_mapping : nil) unless @language_mapping.nil?
|
199
|
-
defn['@context'] = @context if @context
|
200
|
-
defn['@nest'] = @nest if @nest
|
201
|
-
defn['@index'] = @index if @index
|
202
|
-
defn['@prefix'] = @prefix unless @prefix.nil?
|
203
|
-
defn
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
##
|
208
|
-
# Turn this into a source for a new instantiation
|
209
|
-
# FIXME: context serialization
|
210
|
-
# @return [String]
|
211
|
-
def to_rb
|
212
|
-
defn = [%(TermDefinition.new\(#{term.inspect})]
|
213
|
-
%w(id index type_mapping container_mapping language_mapping direction_mapping reverse_property nest simple prefix context protected).each do |acc|
|
214
|
-
v = instance_variable_get("@#{acc}".to_sym)
|
215
|
-
v = v.to_s if v.is_a?(RDF::Term)
|
216
|
-
if acc == 'container_mapping'
|
217
|
-
v = v.to_a
|
218
|
-
v << '@set' if as_set?
|
219
|
-
v = v.first if v.length <= 1
|
220
|
-
end
|
221
|
-
defn << "#{acc}: #{v.inspect}" if v
|
222
|
-
end
|
223
|
-
defn.join(', ') + ")"
|
224
|
-
end
|
225
|
-
|
226
|
-
# If container mapping was defined along with @set
|
227
|
-
# @return [Boolean]
|
228
|
-
def as_set?; @as_set || false; end
|
229
|
-
|
230
|
-
# Check if term definitions are identical, modulo @protected
|
231
|
-
# @return [Boolean]
|
232
|
-
def ==(other)
|
233
|
-
other.is_a?(TermDefinition) &&
|
234
|
-
id == other.id &&
|
235
|
-
term == other.term &&
|
236
|
-
type_mapping == other.type_mapping &&
|
237
|
-
container_mapping == other.container_mapping &&
|
238
|
-
nest == other.nest &&
|
239
|
-
language_mapping == other.language_mapping &&
|
240
|
-
direction_mapping == other.direction_mapping &&
|
241
|
-
reverse_property == other.reverse_property &&
|
242
|
-
simple == other.simple &&
|
243
|
-
index == other.index &&
|
244
|
-
context == other.context &&
|
245
|
-
prefix? == other.prefix? &&
|
246
|
-
as_set? == other.as_set?
|
247
|
-
end
|
248
|
-
|
249
|
-
def inspect
|
250
|
-
v = %w([TD)
|
251
|
-
v << "id=#{@id}"
|
252
|
-
v << "index=#{index.inspect}" unless index.nil?
|
253
|
-
v << "term=#{@term}"
|
254
|
-
v << "rev" if reverse_property
|
255
|
-
v << "container=#{container_mapping}" if container_mapping
|
256
|
-
v << "as_set=#{as_set?.inspect}"
|
257
|
-
v << "lang=#{language_mapping.inspect}" unless language_mapping.nil?
|
258
|
-
v << "dir=#{direction_mapping.inspect}" unless direction_mapping.nil?
|
259
|
-
v << "type=#{type_mapping}" unless type_mapping.nil?
|
260
|
-
v << "nest=#{nest.inspect}" unless nest.nil?
|
261
|
-
v << "simple=true" if @simple
|
262
|
-
v << "protected=true" if @protected
|
263
|
-
v << "prefix=#{@prefix.inspect}" unless @prefix.nil?
|
264
|
-
v << "has-context" unless context.nil?
|
265
|
-
v.join(" ") + "]"
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
53
|
# The base.
|
270
54
|
#
|
271
55
|
# @return [RDF::URI] Current base IRI, used for expanding relative IRIs.
|
272
56
|
attr_reader :base
|
273
57
|
|
274
|
-
# The base.
|
275
|
-
#
|
276
|
-
# @return [RDF::URI] Document base IRI, to initialize `base`.
|
277
|
-
attr_reader :doc_base
|
278
|
-
|
279
58
|
# @return [RDF::URI] base IRI of the context, if loaded remotely.
|
280
59
|
attr_accessor :context_base
|
281
60
|
|
@@ -316,9 +95,6 @@ module JSON::LD
|
|
316
95
|
# @return [Hash{Symbol => Object}] Global options used in generating IRIs
|
317
96
|
attr_accessor :options
|
318
97
|
|
319
|
-
# @return [Context] A context provided to us that we can use without re-serializing XXX
|
320
|
-
attr_accessor :provided_context
|
321
|
-
|
322
98
|
# @return [BlankNodeNamer]
|
323
99
|
attr_accessor :namer
|
324
100
|
|
@@ -328,187 +104,117 @@ module JSON::LD
|
|
328
104
|
# @see #initialize
|
329
105
|
# @see #parse
|
330
106
|
# @param [String, #read, Array, Hash, Context] local_context
|
331
|
-
# @
|
332
|
-
# on a remote context load error, syntax error, or a reference to a term which is not defined.
|
333
|
-
# @return [Context]
|
334
|
-
def self.parse(local_context, protected: false, override_protected: false, propagate: true, **options)
|
335
|
-
self.new(**options).parse(local_context, protected: false, override_protected: override_protected, propagate: propagate)
|
336
|
-
end
|
337
|
-
|
338
|
-
##
|
339
|
-
# Create new evaluation context
|
340
|
-
# @param [Hash] options
|
341
|
-
# @option options [String, #to_s] :base
|
107
|
+
# @param [String, #to_s] base (nil)
|
342
108
|
# The Base IRI to use when expanding the document. This overrides the value of `input` if it is a _IRI_. If not specified and `input` is not an _IRI_, the base IRI defaults to the current document IRI if in a browser context, or the empty string if there is no document context.
|
343
|
-
# @
|
344
|
-
#
|
345
|
-
# @
|
346
|
-
#
|
347
|
-
# @
|
348
|
-
#
|
349
|
-
# @
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
end
|
359
|
-
self.processingMode = options[:processingMode] if options.has_key?(:processingMode)
|
360
|
-
@term_definitions = {}
|
361
|
-
@iri_to_term = {
|
362
|
-
RDF.to_uri.to_s => "rdf",
|
363
|
-
RDF::XSD.to_uri.to_s => "xsd"
|
364
|
-
}
|
365
|
-
@namer = BlankNodeMapper.new("t")
|
366
|
-
|
367
|
-
@options = options
|
368
|
-
|
369
|
-
# Load any defined prefixes
|
370
|
-
(options[:prefixes] || {}).each_pair do |k, v|
|
371
|
-
next if k.nil?
|
372
|
-
@iri_to_term[v.to_s] = k
|
373
|
-
@term_definitions[k.to_s] = TermDefinition.new(k, id: v.to_s, simple: true, prefix: true)
|
374
|
-
end
|
375
|
-
|
376
|
-
self.vocab = options[:vocab] if options[:vocab]
|
377
|
-
self.default_language = options[:language] if options[:language] =~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/
|
378
|
-
@term_definitions = options[:term_definitions] if options[:term_definitions]
|
379
|
-
|
380
|
-
#log_debug("init") {"iri_to_term: #{iri_to_term.inspect}"}
|
381
|
-
|
382
|
-
yield(self) if block_given?
|
383
|
-
end
|
384
|
-
|
385
|
-
##
|
386
|
-
# Initial context, without mappings, vocab or default language
|
387
|
-
#
|
388
|
-
# @return [Boolean]
|
389
|
-
def empty?
|
390
|
-
@term_definitions.empty? && self.vocab.nil? && self.default_language.nil?
|
391
|
-
end
|
392
|
-
|
393
|
-
# @param [String] value must be an absolute IRI
|
394
|
-
def base=(value, **options)
|
395
|
-
if value
|
396
|
-
raise JsonLdError::InvalidBaseIRI, "@base must be a string: #{value.inspect}" unless value.is_a?(String) || value.is_a?(RDF::URI)
|
397
|
-
value = RDF::URI(value).dup
|
398
|
-
value = @base.join(value) if @base && value.relative?
|
399
|
-
@base = value
|
400
|
-
@base.canonicalize! if @options[:canonicalize]
|
401
|
-
raise JsonLdError::InvalidBaseIRI, "@base must be an absolute IRI: #{value.inspect}" unless @base.absolute? || !@options[:validate]
|
402
|
-
@base
|
403
|
-
else
|
404
|
-
@base = nil
|
405
|
-
end
|
406
|
-
|
407
|
-
end
|
408
|
-
|
409
|
-
# @param [String] value
|
410
|
-
def default_language=(value, **options)
|
411
|
-
@default_language = case value
|
412
|
-
when String
|
413
|
-
# Warn on an invalid language tag, unless :validate is true, in which case it's an error
|
414
|
-
if value !~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/
|
415
|
-
warn "@language must be valid BCP47: #{value.inspect}"
|
416
|
-
end
|
417
|
-
options[:lowercaseLanguage] ? value.downcase : value
|
418
|
-
when nil
|
419
|
-
nil
|
109
|
+
# @param [Boolean] override_protected (false)
|
110
|
+
# Protected terms may be cleared.
|
111
|
+
# @param [Boolean] propagate (true)
|
112
|
+
# If false, retains any previously defined term, which can be rolled back when the descending into a new node object changes.
|
113
|
+
# @raise [JsonLdError]
|
114
|
+
# on a remote context load error, syntax error, or a reference to a term which is not defined.
|
115
|
+
# @return [Context]
|
116
|
+
def self.parse(local_context,
|
117
|
+
base: nil,
|
118
|
+
override_protected: false,
|
119
|
+
propagate: true,
|
120
|
+
**options)
|
121
|
+
c = self.new(**options)
|
122
|
+
if local_context.respond_to?(:empty?) && local_context.empty?
|
123
|
+
c
|
420
124
|
else
|
421
|
-
|
125
|
+
c.parse(local_context,
|
126
|
+
base: base,
|
127
|
+
override_protected: override_protected,
|
128
|
+
propagate: propagate)
|
422
129
|
end
|
423
130
|
end
|
424
131
|
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
end
|
132
|
+
##
|
133
|
+
# Class-level cache used for retaining parsed remote contexts.
|
134
|
+
#
|
135
|
+
# @return [RDF::Util::Cache]
|
136
|
+
# @private
|
137
|
+
def self.cache
|
138
|
+
@cache ||= RDF::Util::Cache.new(CACHE_SIZE)
|
433
139
|
end
|
434
140
|
|
435
141
|
##
|
436
|
-
#
|
142
|
+
# Class-level cache inverse contexts.
|
437
143
|
#
|
438
|
-
#
|
439
|
-
#
|
440
|
-
|
441
|
-
|
442
|
-
# @param [String, Number] expected (nil)
|
443
|
-
# @return [String]
|
444
|
-
def processingMode(expected = nil)
|
445
|
-
case expected
|
446
|
-
when 1.0, 'json-ld-1.0'
|
447
|
-
@processingMode == 'json-ld-1.0'
|
448
|
-
when 1.1, 'json-ld-1.1'
|
449
|
-
@processingMode ||= 'json-ld-1.1'
|
450
|
-
@processingMode == 'json-ld-1.1'
|
451
|
-
when nil
|
452
|
-
@processingMode
|
453
|
-
else
|
454
|
-
false
|
455
|
-
end
|
144
|
+
# @return [RDF::Util::Cache]
|
145
|
+
# @private
|
146
|
+
def self.inverse_cache
|
147
|
+
@inverse_cache ||= RDF::Util::Cache.new(CACHE_SIZE)
|
456
148
|
end
|
457
149
|
|
458
150
|
##
|
459
|
-
#
|
460
|
-
#
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
151
|
+
# @private
|
152
|
+
# Allow caching of well-known contexts
|
153
|
+
def self.new(**options)
|
154
|
+
if (options.keys - [
|
155
|
+
:compactArrays,
|
156
|
+
:documentLoader,
|
157
|
+
:extractAllScripts,
|
158
|
+
:ordered,
|
159
|
+
:processingMode,
|
160
|
+
:validate
|
161
|
+
]).empty?
|
162
|
+
# allow caching
|
163
|
+
key = options.hash
|
164
|
+
INITIAL_CONTEXTS[key] ||= begin
|
165
|
+
context = JSON::LD::Context.allocate
|
166
|
+
context.send(:initialize, **options)
|
167
|
+
context.freeze
|
168
|
+
context.term_definitions.freeze
|
169
|
+
context
|
475
170
|
end
|
476
|
-
@processingMode = value
|
477
171
|
else
|
478
|
-
|
172
|
+
# Don't try to cache
|
173
|
+
context = JSON::LD::Context.allocate
|
174
|
+
context.send(:initialize, **options)
|
175
|
+
context
|
479
176
|
end
|
480
177
|
end
|
481
178
|
|
482
|
-
|
483
|
-
#
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
when nil
|
498
|
-
nil
|
499
|
-
else
|
500
|
-
raise JsonLdError::InvalidVocabMapping, "@vocab must be an IRI: #{value.inspect}"
|
179
|
+
##
|
180
|
+
# Create new evaluation context
|
181
|
+
# @param [Hash] options
|
182
|
+
# @option options [Hash{Symbol => String}] :prefixes
|
183
|
+
# See `RDF::Reader#initialize`
|
184
|
+
# @option options [String, #to_s] :vocab
|
185
|
+
# Initial value for @vocab
|
186
|
+
# @option options [String, #to_s] :language
|
187
|
+
# Initial value for @langauge
|
188
|
+
# @yield [ec]
|
189
|
+
# @yieldparam [Context]
|
190
|
+
# @return [Context]
|
191
|
+
def initialize(**options)
|
192
|
+
if options[:processingMode] == 'json-ld-1.0'
|
193
|
+
@processingMode = 'json-ld-1.0'
|
501
194
|
end
|
502
|
-
|
195
|
+
@term_definitions = {}
|
196
|
+
@iri_to_term = {
|
197
|
+
RDF.to_uri.to_s => "rdf",
|
198
|
+
RDF::XSD.to_uri.to_s => "xsd"
|
199
|
+
}
|
200
|
+
@namer = BlankNodeMapper.new("t")
|
503
201
|
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
202
|
+
@options = options
|
203
|
+
|
204
|
+
# Load any defined prefixes
|
205
|
+
(options[:prefixes] || {}).each_pair do |k, v|
|
206
|
+
next if k.nil?
|
207
|
+
@iri_to_term[v.to_s] = k
|
208
|
+
@term_definitions[k.to_s] = TermDefinition.new(k, id: v.to_s, simple: true, prefix: true)
|
209
|
+
end
|
210
|
+
|
211
|
+
self.vocab = options[:vocab] if options[:vocab]
|
212
|
+
self.default_language = options[:language] if options[:language] =~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/
|
213
|
+
@term_definitions = options[:term_definitions] if options[:term_definitions]
|
214
|
+
|
215
|
+
#log_debug("init") {"iri_to_term: #{iri_to_term.inspect}"}
|
216
|
+
|
217
|
+
yield(self) if block_given?
|
512
218
|
end
|
513
219
|
|
514
220
|
# Create an Evaluation Context
|
@@ -521,11 +227,12 @@ module JSON::LD
|
|
521
227
|
#
|
522
228
|
#
|
523
229
|
# @param [String, #read, Array, Hash, Context] local_context
|
524
|
-
# @param [
|
525
|
-
#
|
230
|
+
# @param [String, #to_s] base
|
231
|
+
# The Base IRI to use when expanding the document. This overrides the value of `input` if it is a _IRI_. If not specified and `input` is not an _IRI_, the base IRI defaults to the current document IRI if in a browser context, or the empty string if there is no document context.
|
526
232
|
# @param [Boolean] override_protected Protected terms may be cleared.
|
527
|
-
# @param [Boolean] propagate
|
233
|
+
# @param [Boolean] propagate (true)
|
528
234
|
# If false, retains any previously defined term, which can be rolled back when the descending into a new node object changes.
|
235
|
+
# @param [Array<String>] remote_contexts ([])
|
529
236
|
# @param [Boolean] validate_scoped (true).
|
530
237
|
# Validate scoped context, loading if necessary.
|
531
238
|
# If false, do not load scoped contexts.
|
@@ -534,13 +241,12 @@ module JSON::LD
|
|
534
241
|
# @return [Context]
|
535
242
|
# @see https://www.w3.org/TR/json-ld11-api/index.html#context-processing-algorithm
|
536
243
|
def parse(local_context,
|
537
|
-
|
538
|
-
protected: false,
|
244
|
+
base: nil,
|
539
245
|
override_protected: false,
|
540
246
|
propagate: true,
|
247
|
+
remote_contexts: [],
|
541
248
|
validate_scoped: true)
|
542
249
|
result = self.dup
|
543
|
-
result.provided_context = local_context if self.empty?
|
544
250
|
# Early check for @propagate, which can only appear in a local context
|
545
251
|
propagate = local_context.is_a?(Hash) ? local_context.fetch('@propagate', propagate) : propagate
|
546
252
|
result.previous_context ||= result.dup unless propagate
|
@@ -549,7 +255,7 @@ module JSON::LD
|
|
549
255
|
|
550
256
|
local_context.each do |context|
|
551
257
|
case context
|
552
|
-
when nil
|
258
|
+
when nil,false
|
553
259
|
# 3.1 If the `override_protected` is false, and the active context contains protected terms, an error is raised.
|
554
260
|
if override_protected || result.term_definitions.values.none?(&:protected?)
|
555
261
|
null_context = Context.new(**options)
|
@@ -561,28 +267,26 @@ module JSON::LD
|
|
561
267
|
end
|
562
268
|
when Context
|
563
269
|
#log_debug("parse") {"context: #{context.inspect}"}
|
564
|
-
result = context
|
270
|
+
result = result.merge(context)
|
565
271
|
when IO, StringIO
|
566
272
|
#log_debug("parse") {"io: #{context}"}
|
567
273
|
# Load context document, if it is an open file
|
568
274
|
begin
|
569
275
|
ctx = JSON.load(context)
|
570
276
|
raise JSON::LD::JsonLdError::InvalidRemoteContext, "Context missing @context key" if @options[:validate] && ctx['@context'].nil?
|
571
|
-
result = result.
|
572
|
-
result.provided_context = ctx["@context"] if [context] == local_context
|
573
|
-
result
|
277
|
+
result = result.parse(ctx["@context"] ? ctx["@context"] : {})
|
574
278
|
rescue JSON::ParserError => e
|
575
279
|
#log_debug("parse") {"Failed to parse @context from remote document at #{context}: #{e.message}"}
|
576
280
|
raise JSON::LD::JsonLdError::InvalidRemoteContext, "Failed to parse remote context at #{context}: #{e.message}" if @options[:validate]
|
577
|
-
self
|
281
|
+
self
|
578
282
|
end
|
579
283
|
when String, RDF::URI
|
580
284
|
#log_debug("parse") {"remote: #{context}, base: #{result.context_base || result.base}"}
|
581
285
|
|
582
286
|
# 3.2.1) Set context to the result of resolving value against the base IRI which is established as specified in section 5.1 Establishing a Base URI of [RFC3986]. Only the basic algorithm in section 5.2 of [RFC3986] is used; neither Syntax-Based Normalization nor Scheme-Based Normalization are performed. Characters additionally allowed in IRI references are treated in the same way that unreserved characters are treated in URI references, per section 6.5 of [RFC3987].
|
583
|
-
context = RDF::URI(result.context_base ||
|
287
|
+
context = RDF::URI(result.context_base || base).join(context)
|
584
288
|
context_canon = context.canonicalize
|
585
|
-
context_canon.scheme
|
289
|
+
context_canon.scheme = 'http' if context_canon.scheme == 'https'
|
586
290
|
|
587
291
|
# If validating a scoped context which has already been loaded, skip to the next one
|
588
292
|
next if !validate_scoped && remote_contexts.include?(context.to_s)
|
@@ -590,11 +294,7 @@ module JSON::LD
|
|
590
294
|
remote_contexts << context.to_s
|
591
295
|
raise JsonLdError::ContextOverflow, "#{context}" if remote_contexts.length >= MAX_CONTEXTS_LOADED
|
592
296
|
|
593
|
-
|
594
|
-
context_no_base.base = nil
|
595
|
-
context_no_base.context_base = context.to_s
|
596
|
-
|
597
|
-
if PRELOADED[context_canon.to_s]
|
297
|
+
cached_context = if PRELOADED[context_canon.to_s]
|
598
298
|
# If we have a cached context, merge it into the current context (result) and use as the new context
|
599
299
|
#log_debug("parse") {"=> cached_context: #{context_canon.to_s.inspect}"}
|
600
300
|
|
@@ -603,10 +303,10 @@ module JSON::LD
|
|
603
303
|
#log_debug("parse") {"=> (call)"}
|
604
304
|
PRELOADED[context_canon.to_s] = PRELOADED[context_canon.to_s].call
|
605
305
|
end
|
606
|
-
|
306
|
+
PRELOADED[context_canon.to_s]
|
607
307
|
else
|
608
308
|
# Load context document, if it is a string
|
609
|
-
begin
|
309
|
+
Context.cache[context_canon.to_s] ||= begin
|
610
310
|
context_opts = @options.merge(
|
611
311
|
profile: 'http://www.w3.org/ns/json-ld#context',
|
612
312
|
requestProfile: 'http://www.w3.org/ns/json-ld#context',
|
@@ -615,31 +315,30 @@ module JSON::LD
|
|
615
315
|
JSON::LD::API.loadRemoteDocument(context.to_s, **context_opts) do |remote_doc|
|
616
316
|
# 3.2.5) Dereference context. If the dereferenced document has no top-level JSON object with an @context member, an invalid remote context has been detected and processing is aborted; otherwise, set context to the value of that member.
|
617
317
|
raise JsonLdError::InvalidRemoteContext, "#{context}" unless remote_doc.document.is_a?(Hash) && remote_doc.document.has_key?('@context')
|
618
|
-
|
318
|
+
|
319
|
+
# Parse stand-alone
|
320
|
+
ctx = Context.new(unfrozen: true, **options).dup
|
321
|
+
ctx.context_base = context.to_s
|
322
|
+
ctx = ctx.parse(remote_doc.document['@context'], remote_contexts: remote_contexts.dup)
|
323
|
+
ctx.instance_variable_set(:@base, nil)
|
324
|
+
ctx
|
619
325
|
end
|
620
326
|
rescue JsonLdError::LoadingDocumentFailed => e
|
621
327
|
#log_debug("parse") {"Failed to retrieve @context from remote document at #{context_no_base.context_base.inspect}: #{e.message}"}
|
622
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{
|
328
|
+
raise JsonLdError::LoadingRemoteContextFailed, "#{context}: #{e.message}", e.backtrace
|
623
329
|
rescue JsonLdError
|
624
330
|
raise
|
625
331
|
rescue StandardError => e
|
626
332
|
#log_debug("parse") {"Failed to retrieve @context from remote document at #{context_no_base.context_base.inspect}: #{e.message}"}
|
627
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{
|
333
|
+
raise JsonLdError::LoadingRemoteContextFailed, "#{context}: #{e.message}", e.backtrace
|
628
334
|
end
|
629
|
-
|
630
|
-
# 3.2.6) Set context to the result of recursively calling this algorithm, passing context no base for active context, context for local context, and remote contexts.
|
631
|
-
context = context_no_base.parse(context,
|
632
|
-
remote_contexts: remote_contexts.dup,
|
633
|
-
protected: protected,
|
634
|
-
override_protected: override_protected,
|
635
|
-
propagate: propagate,
|
636
|
-
validate_scoped: validate_scoped)
|
637
|
-
PRELOADED[context_canon.to_s] = context.dup
|
638
|
-
context.provided_context = result.provided_context
|
639
335
|
end
|
640
|
-
|
336
|
+
|
337
|
+
# Merge loaded context noting protected term overriding
|
338
|
+
context = result.merge(cached_context, override_protected: override_protected)
|
339
|
+
|
340
|
+
context.previous_context = self unless propagate
|
641
341
|
result = context
|
642
|
-
#log_debug("parse") {"=> provided_context: #{context.inspect}"}
|
643
342
|
when Hash
|
644
343
|
context = context.dup # keep from modifying a hash passed as a param
|
645
344
|
|
@@ -658,31 +357,33 @@ module JSON::LD
|
|
658
357
|
# Retrieve remote context and merge the remaining context object into the result.
|
659
358
|
raise JsonLdError::InvalidContextEntry, "@import may only be used in 1.1 mode}" if result.processingMode("json-ld-1.0")
|
660
359
|
raise JsonLdError::InvalidImportValue, "@import must be a string: #{context['@import'].inspect}" unless context['@import'].is_a?(String)
|
661
|
-
|
360
|
+
import_loc = RDF::URI(result.context_base || base).join(context['@import'])
|
662
361
|
begin
|
663
362
|
context_opts = @options.merge(
|
664
363
|
profile: 'http://www.w3.org/ns/json-ld#context',
|
665
364
|
requestProfile: 'http://www.w3.org/ns/json-ld#context',
|
666
365
|
base: nil)
|
667
366
|
context_opts.delete(:headers)
|
668
|
-
|
669
|
-
|
670
|
-
|
367
|
+
# FIXME: should cache this, but ContextCache is for parsed contexts
|
368
|
+
JSON::LD::API.loadRemoteDocument(import_loc, **context_opts) do |remote_doc|
|
369
|
+
# Dereference import_loc. If the dereferenced document has no top-level JSON object with an @context member, an invalid remote context has been detected and processing is aborted; otherwise, set context to the value of that member.
|
370
|
+
raise JsonLdError::InvalidRemoteContext, "#{import_loc}" unless remote_doc.document.is_a?(Hash) && remote_doc.document.has_key?('@context')
|
671
371
|
import_context = remote_doc.document['@context']
|
372
|
+
import_context.delete('@base')
|
672
373
|
raise JsonLdError::InvalidRemoteContext, "#{import_context.to_json} must be an object" unless import_context.is_a?(Hash)
|
673
374
|
raise JsonLdError::InvalidContextEntry, "#{import_context.to_json} must not include @import entry" if import_context.has_key?('@import')
|
674
375
|
context.delete(key)
|
675
376
|
context = import_context.merge(context)
|
676
377
|
end
|
677
378
|
rescue JsonLdError::LoadingDocumentFailed => e
|
678
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{
|
379
|
+
raise JsonLdError::LoadingRemoteContextFailed, "#{import_loc}: #{e.message}", e.backtrace
|
679
380
|
rescue JsonLdError
|
680
381
|
raise
|
681
382
|
rescue StandardError => e
|
682
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{
|
383
|
+
raise JsonLdError::LoadingRemoteContextFailed, "#{import_loc}: #{e.message}", e.backtrace
|
683
384
|
end
|
684
385
|
else
|
685
|
-
result.send(setter, context[key], remote_contexts: remote_contexts
|
386
|
+
result.send(setter, context[key], remote_contexts: remote_contexts)
|
686
387
|
end
|
687
388
|
context.delete(key)
|
688
389
|
end
|
@@ -693,8 +394,9 @@ module JSON::LD
|
|
693
394
|
context.each_key do |key|
|
694
395
|
# ... where key is not @base, @vocab, @language, or @version
|
695
396
|
result.create_term_definition(context, key, defined,
|
397
|
+
base: base,
|
696
398
|
override_protected: override_protected,
|
697
|
-
protected: context
|
399
|
+
protected: context['@protected'],
|
698
400
|
remote_contexts: remote_contexts.dup,
|
699
401
|
validate_scoped: validate_scoped
|
700
402
|
) unless NON_TERMDEF_KEYS.include?(key)
|
@@ -711,28 +413,29 @@ module JSON::LD
|
|
711
413
|
# Merge in a context, creating a new context with updates from `context`
|
712
414
|
#
|
713
415
|
# @param [Context] context
|
416
|
+
# @param [Boolean] override_protected Allow or disallow protected terms to be changed
|
714
417
|
# @return [Context]
|
715
|
-
def merge(context)
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
418
|
+
def merge(context, override_protected: false)
|
419
|
+
ctx = Context.new(term_definitions: self.term_definitions, standard_prefixes: options[:standard_prefixes])
|
420
|
+
ctx.context_base = context.context_base || self.context_base
|
421
|
+
ctx.default_language = context.default_language || self.default_language
|
422
|
+
ctx.default_direction = context.default_direction || self.default_direction
|
423
|
+
ctx.vocab = context.vocab || self.vocab
|
424
|
+
ctx.base = self.base unless self.base.nil?
|
425
|
+
if !override_protected
|
426
|
+
ctx.term_definitions.each do |term, definition|
|
427
|
+
next unless definition.protected? && (other = context.term_definitions[term])
|
428
|
+
unless definition == other
|
429
|
+
raise JSON::LD::JsonLdError::ProtectedTermRedefinition, "Attempt to redefine protected term #{term}"
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
720
433
|
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
def merge!(context)
|
727
|
-
# FIXME: if new context removes the default language, this won't do anything
|
728
|
-
self.default_language = context.default_language if context.default_language
|
729
|
-
self.vocab = context.vocab if context.vocab
|
730
|
-
self.base = context.base if context.base
|
731
|
-
|
732
|
-
# Merge in Term Definitions
|
733
|
-
term_definitions.merge!(context.term_definitions)
|
734
|
-
@inverse_context = nil # Re-build after term definitions set
|
735
|
-
self
|
434
|
+
# Add term definitions
|
435
|
+
context.term_definitions.each do |term, definition|
|
436
|
+
ctx.term_definitions[term] = definition
|
437
|
+
end
|
438
|
+
ctx
|
736
439
|
end
|
737
440
|
|
738
441
|
# The following constants are used to reduce object allocations in #create_term_definition below
|
@@ -755,10 +458,9 @@ module JSON::LD
|
|
755
458
|
# @param [Hash] local_context
|
756
459
|
# @param [String] term
|
757
460
|
# @param [Hash] defined
|
461
|
+
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
758
462
|
# @param [Boolean] protected if true, causes all terms to be marked protected
|
759
463
|
# @param [Boolean] override_protected Protected terms may be cleared.
|
760
|
-
# @param [Boolean] propagate
|
761
|
-
# Context is propagated across node objects.
|
762
464
|
# @param [Array<String>] remote_contexts
|
763
465
|
# @param [Boolean] validate_scoped (true).
|
764
466
|
# Validate scoped context, loading if necessary.
|
@@ -767,8 +469,9 @@ module JSON::LD
|
|
767
469
|
# Represents a cyclical term dependency
|
768
470
|
# @see https://www.w3.org/TR/json-ld11-api/index.html#create-term-definition
|
769
471
|
def create_term_definition(local_context, term, defined,
|
472
|
+
base: nil,
|
770
473
|
override_protected: false,
|
771
|
-
protected:
|
474
|
+
protected: nil,
|
772
475
|
remote_contexts: [],
|
773
476
|
validate_scoped: true)
|
774
477
|
# Expand a string value, unless it matches a keyword
|
@@ -985,9 +688,17 @@ module JSON::LD
|
|
985
688
|
|
986
689
|
if value.has_key?('@context')
|
987
690
|
begin
|
988
|
-
self.parse(value['@context'],
|
691
|
+
new_ctx = self.parse(value['@context'],
|
692
|
+
base: base,
|
693
|
+
override_protected: true,
|
694
|
+
remote_contexts: remote_contexts,
|
695
|
+
validate_scoped: false)
|
989
696
|
# Record null context in array form
|
990
|
-
definition.context =
|
697
|
+
definition.context = case value['@context']
|
698
|
+
when String then new_ctx.context_base
|
699
|
+
when nil then [nil]
|
700
|
+
else value['@context']
|
701
|
+
end
|
991
702
|
rescue JsonLdError => e
|
992
703
|
raise JsonLdError::InvalidScopedContext, "Term definition for #{term.inspect} contains illegal value for @context: #{e.message}"
|
993
704
|
end
|
@@ -1045,9 +756,130 @@ module JSON::LD
|
|
1045
756
|
|
1046
757
|
term_definitions[term] = definition
|
1047
758
|
defined[term] = true
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
759
|
+
end
|
760
|
+
|
761
|
+
##
|
762
|
+
# Initial context, without mappings, vocab or default language
|
763
|
+
#
|
764
|
+
# @return [Boolean]
|
765
|
+
def empty?
|
766
|
+
@term_definitions.empty? && self.vocab.nil? && self.default_language.nil?
|
767
|
+
end
|
768
|
+
|
769
|
+
# @param [String] value must be an absolute IRI
|
770
|
+
def base=(value, **options)
|
771
|
+
if value
|
772
|
+
raise JsonLdError::InvalidBaseIRI, "@base must be a string: #{value.inspect}" unless value.is_a?(String) || value.is_a?(RDF::URI)
|
773
|
+
value = RDF::URI(value)
|
774
|
+
value = @base.join(value) if @base && value.relative?
|
775
|
+
# still might be relative to document
|
776
|
+
@base = value
|
777
|
+
else
|
778
|
+
@base = false
|
779
|
+
end
|
780
|
+
|
781
|
+
end
|
782
|
+
|
783
|
+
# @param [String] value
|
784
|
+
def default_language=(value, **options)
|
785
|
+
@default_language = case value
|
786
|
+
when String
|
787
|
+
# Warn on an invalid language tag, unless :validate is true, in which case it's an error
|
788
|
+
if value !~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/
|
789
|
+
warn "@language must be valid BCP47: #{value.inspect}"
|
790
|
+
end
|
791
|
+
options[:lowercaseLanguage] ? value.downcase : value
|
792
|
+
when nil
|
793
|
+
nil
|
794
|
+
else
|
795
|
+
raise JsonLdError::InvalidDefaultLanguage, "@language must be a string: #{value.inspect}"
|
796
|
+
end
|
797
|
+
end
|
798
|
+
|
799
|
+
# @param [String] value
|
800
|
+
def default_direction=(value, **options)
|
801
|
+
@default_direction = if value
|
802
|
+
raise JsonLdError::InvalidBaseDirection, "@direction must be one or 'ltr', or 'rtl': #{value.inspect}" unless %w(ltr rtl).include?(value)
|
803
|
+
value
|
804
|
+
else
|
805
|
+
nil
|
806
|
+
end
|
807
|
+
end
|
808
|
+
|
809
|
+
##
|
810
|
+
# Retrieve, or check processing mode.
|
811
|
+
#
|
812
|
+
# * With no arguments, retrieves the current set processingMode.
|
813
|
+
# * With an argument, verifies that the processingMode is at least that provided, either as an integer, or a string of the form "json-ld-1.x"
|
814
|
+
# * If expecting 1.1, and not set, it has the side-effect of setting mode to json-ld-1.1.
|
815
|
+
#
|
816
|
+
# @param [String, Number] expected (nil)
|
817
|
+
# @return [String]
|
818
|
+
def processingMode(expected = nil)
|
819
|
+
case expected
|
820
|
+
when 1.0, 'json-ld-1.0'
|
821
|
+
@processingMode == 'json-ld-1.0'
|
822
|
+
when 1.1, 'json-ld-1.1'
|
823
|
+
@processingMode.nil? || @processingMode == 'json-ld-1.1'
|
824
|
+
when nil
|
825
|
+
@processingMode || 'json-ld-1.1'
|
826
|
+
else
|
827
|
+
false
|
828
|
+
end
|
829
|
+
end
|
830
|
+
|
831
|
+
##
|
832
|
+
# Set processing mode.
|
833
|
+
#
|
834
|
+
# * With an argument, verifies that the processingMode is at least that provided, either as an integer, or a string of the form "json-ld-1.x"
|
835
|
+
#
|
836
|
+
# If contex has a @version member, it's value MUST be 1.1, otherwise an "invalid @version value" has been detected, and processing is aborted.
|
837
|
+
# If processingMode has been set, and it is not "json-ld-1.1", a "processing mode conflict" has been detecting, and processing is aborted.
|
838
|
+
#
|
839
|
+
# @param [String, Number] value
|
840
|
+
# @return [String]
|
841
|
+
# @raise [JsonLdError::ProcessingModeConflict]
|
842
|
+
def processingMode=(value = nil, **options)
|
843
|
+
value = "json-ld-1.1" if value == 1.1
|
844
|
+
case value
|
845
|
+
when "json-ld-1.0", "json-ld-1.1"
|
846
|
+
if @processingMode && @processingMode != value
|
847
|
+
raise JsonLdError::ProcessingModeConflict, "#{value} not compatible with #{@processingMode}"
|
848
|
+
end
|
849
|
+
@processingMode = value
|
850
|
+
else
|
851
|
+
raise JsonLdError::InvalidVersionValue, value.inspect
|
852
|
+
end
|
853
|
+
end
|
854
|
+
|
855
|
+
# If context has a @vocab member: if its value is not a valid absolute IRI or null trigger an INVALID_VOCAB_MAPPING error; otherwise set the active context's vocabulary mapping to its value and remove the @vocab member from context.
|
856
|
+
# @param [String] value must be an absolute IRI
|
857
|
+
def vocab=(value, **options)
|
858
|
+
@vocab = case value
|
859
|
+
when /_:/
|
860
|
+
# BNode vocab is deprecated
|
861
|
+
warn "[DEPRECATION] Blank Node vocabularies deprecated in JSON-LD 1.1." if @options[:validate] && processingMode("json-ld-1.1")
|
862
|
+
value
|
863
|
+
when String, RDF::URI
|
864
|
+
if (RDF::URI(value.to_s).relative? && processingMode("json-ld-1.0"))
|
865
|
+
raise JsonLdError::InvalidVocabMapping, "@vocab must be an absolute IRI in 1.0 mode: #{value.inspect}"
|
866
|
+
end
|
867
|
+
expand_iri(value.to_s, vocab: true, documentRelative: true)
|
868
|
+
when nil
|
869
|
+
nil
|
870
|
+
else
|
871
|
+
raise JsonLdError::InvalidVocabMapping, "@vocab must be an IRI: #{value.inspect}"
|
872
|
+
end
|
873
|
+
end
|
874
|
+
|
875
|
+
# Set propagation
|
876
|
+
# @note: by the time this is called, the work has already been done.
|
877
|
+
#
|
878
|
+
# @param [Boolean] value
|
879
|
+
def propagate=(value, **options)
|
880
|
+
raise JsonLdError::InvalidContextEntry, "@propagate may only be set in 1.1 mode" if processingMode("json-ld-1.0")
|
881
|
+
raise JsonLdError::InvalidPropagateValue, "@propagate must be boolean valued: #{value.inspect}" unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
882
|
+
value
|
1051
883
|
end
|
1052
884
|
|
1053
885
|
##
|
@@ -1056,40 +888,44 @@ module JSON::LD
|
|
1056
888
|
# If a context was supplied in global options, use that, otherwise, generate one
|
1057
889
|
# from this representation.
|
1058
890
|
#
|
891
|
+
# @param [Array, Hash, Context, IO, StringIO] provided_context (nil)
|
892
|
+
# Original context to use, if available
|
1059
893
|
# @param [Hash{Symbol => Object}] options ({})
|
1060
894
|
# @return [Hash]
|
1061
|
-
def serialize(**options)
|
1062
|
-
#
|
895
|
+
def serialize(provided_context: nil, **options)
|
896
|
+
#log_debug("serlialize: generate context")
|
897
|
+
#log_debug("") {"=> context: #{inspect}"}
|
1063
898
|
use_context = case provided_context
|
1064
899
|
when String, RDF::URI
|
1065
900
|
#log_debug "serlialize: reuse context: #{provided_context.inspect}"
|
1066
901
|
provided_context.to_s
|
1067
|
-
when Hash
|
902
|
+
when Hash
|
903
|
+
#log_debug "serlialize: reuse context: #{provided_context.inspect}"
|
904
|
+
# If it has an @context entry use it, otherwise it is assumed to be the body of a context
|
905
|
+
provided_context.fetch('@context', provided_context)
|
906
|
+
when Array
|
1068
907
|
#log_debug "serlialize: reuse context: #{provided_context.inspect}"
|
1069
908
|
provided_context
|
909
|
+
when IO, StringIO
|
910
|
+
provided_context.rewind
|
911
|
+
JSON.load(provided_context).fetch('@context', {})
|
1070
912
|
else
|
1071
|
-
#log_debug("serlialize: generate context")
|
1072
|
-
#log_debug("") {"=> context: #{inspect}"}
|
1073
913
|
ctx = {}
|
1074
|
-
ctx['@
|
914
|
+
ctx['@version'] = 1.1 if @processingMode == 'json-ld-1.1'
|
915
|
+
ctx['@base'] = base.to_s if base
|
1075
916
|
ctx['@direction'] = default_direction.to_s if default_direction
|
1076
917
|
ctx['@language'] = default_language.to_s if default_language
|
1077
918
|
ctx['@vocab'] = vocab.to_s if vocab
|
1078
919
|
|
1079
920
|
# Term Definitions
|
1080
|
-
term_definitions.
|
1081
|
-
|
1082
|
-
ctx[term] = defn if defn
|
921
|
+
term_definitions.each do |term, defn|
|
922
|
+
ctx[term] = defn.to_context_definition(self)
|
1083
923
|
end
|
1084
|
-
|
1085
|
-
#log_debug("") {"start_doc: context=#{ctx.inspect}"}
|
1086
924
|
ctx
|
1087
925
|
end
|
1088
926
|
|
1089
927
|
# Return hash with @context, or empty
|
1090
|
-
|
1091
|
-
r['@context'] = use_context unless use_context.nil? || use_context.empty?
|
1092
|
-
r
|
928
|
+
use_context.nil? || use_context.empty? ? {} : {'@context' => use_context}
|
1093
929
|
end
|
1094
930
|
|
1095
931
|
##
|
@@ -1202,7 +1038,7 @@ module JSON::LD
|
|
1202
1038
|
# @param [Term, #to_s] term in unexpanded form
|
1203
1039
|
# @return [Array<'@index', '@language', '@index', '@set', '@type', '@id', '@graph'>]
|
1204
1040
|
def container(term)
|
1205
|
-
return [term] if term == '@list'
|
1041
|
+
return Set[term] if term == '@list'
|
1206
1042
|
term = find_definition(term)
|
1207
1043
|
term ? term.container_mapping : Set.new
|
1208
1044
|
end
|
@@ -1317,26 +1153,27 @@ module JSON::LD
|
|
1317
1153
|
#
|
1318
1154
|
# @param [String] value
|
1319
1155
|
# A keyword, term, prefix:suffix or possibly relative IRI
|
1156
|
+
# @param [Boolean] as_string (false) transform RDF::Resource values to string
|
1157
|
+
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1158
|
+
# @param [Hash] defined
|
1159
|
+
# Used during Context Processing.
|
1320
1160
|
# @param [Boolean] documentRelative (false)
|
1321
|
-
# @param [Boolean] vocab (false)
|
1322
1161
|
# @param [Hash] local_context
|
1323
1162
|
# Used during Context Processing.
|
1324
|
-
# @param [
|
1325
|
-
# Used during Context Processing.
|
1326
|
-
# @param [Boolean] as_string (false) transform RDF::Resource values to string
|
1163
|
+
# @param [Boolean] vocab (false)
|
1327
1164
|
# @param [Hash{Symbol => Object}] options
|
1328
1165
|
# @return [RDF::Resource, String]
|
1329
1166
|
# IRI or String, if it's a keyword
|
1330
1167
|
# @raise [JSON::LD::JsonLdError::InvalidIRIMapping] if the value cannot be expanded
|
1331
1168
|
# @see https://www.w3.org/TR/json-ld11-api/#iri-expansion
|
1332
1169
|
def expand_iri(value,
|
1170
|
+
as_string: false,
|
1171
|
+
base: nil,
|
1172
|
+
defined: nil,
|
1333
1173
|
documentRelative: false,
|
1334
|
-
vocab: false,
|
1335
1174
|
local_context: nil,
|
1336
|
-
|
1337
|
-
|
1338
|
-
**options
|
1339
|
-
)
|
1175
|
+
vocab: false,
|
1176
|
+
**options)
|
1340
1177
|
return (value && as_string ? value.to_s : value) unless value.is_a?(String)
|
1341
1178
|
|
1342
1179
|
return value if KEYWORDS.include?(value)
|
@@ -1356,7 +1193,8 @@ module JSON::LD
|
|
1356
1193
|
# If active context has a term definition for value, and the associated mapping is a keyword, return that keyword.
|
1357
1194
|
# If vocab is true and the active context has a term definition for value, return the associated IRI mapping.
|
1358
1195
|
if (v_td = term_definitions[value]) && (vocab || KEYWORDS.include?(v_td.id))
|
1359
|
-
|
1196
|
+
iri = base && v_td.id ? base.join(v_td.id) : v_td.id # vocab might be doc relative
|
1197
|
+
return (as_string ? iri.to_s : iri)
|
1360
1198
|
end
|
1361
1199
|
|
1362
1200
|
# If value contains a colon (:), it is either an absolute IRI or a compact IRI:
|
@@ -1389,18 +1227,32 @@ module JSON::LD
|
|
1389
1227
|
end
|
1390
1228
|
end
|
1391
1229
|
|
1230
|
+
iri = value.is_a?(RDF::URI) ? value : RDF::URI(value)
|
1392
1231
|
result = if vocab && self.vocab
|
1393
1232
|
# If vocab is true, and active context has a vocabulary mapping, return the result of concatenating the vocabulary mapping with value.
|
1394
|
-
|
1395
|
-
|
1396
|
-
|
1397
|
-
|
1398
|
-
|
1399
|
-
|
1233
|
+
# Note that @vocab could still be relative to a document base
|
1234
|
+
(base && self.vocab.is_a?(RDF::URI) && self.vocab.relative? ? base.join(self.vocab) : self.vocab) + value
|
1235
|
+
elsif documentRelative
|
1236
|
+
if iri.absolute?
|
1237
|
+
iri
|
1238
|
+
elsif self.base.is_a?(RDF::URI) && self.base.absolute?
|
1239
|
+
self.base.join(iri)
|
1240
|
+
elsif self.base == false
|
1241
|
+
# No resollution of `@base: null`
|
1242
|
+
iri
|
1243
|
+
elsif base && self.base
|
1244
|
+
base.join(self.base).join(iri)
|
1245
|
+
elsif base
|
1246
|
+
base.join(iri)
|
1247
|
+
else
|
1248
|
+
# Returns a relative IRI in an odd case.
|
1249
|
+
iri
|
1250
|
+
end
|
1251
|
+
elsif local_context && iri.relative?
|
1400
1252
|
# If local context is not null and value is not an absolute IRI, an invalid IRI mapping error has been detected and processing is aborted.
|
1401
1253
|
raise JSON::LD::JsonLdError::InvalidIRIMapping, "not an absolute IRI: #{value}"
|
1402
1254
|
else
|
1403
|
-
|
1255
|
+
iri
|
1404
1256
|
end
|
1405
1257
|
result && as_string ? result.to_s : result
|
1406
1258
|
end
|
@@ -1421,17 +1273,17 @@ module JSON::LD
|
|
1421
1273
|
# Compacts an absolute IRI to the shortest matching term or compact IRI
|
1422
1274
|
#
|
1423
1275
|
# @param [RDF::URI] iri
|
1276
|
+
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1424
1277
|
# @param [Object] value
|
1425
1278
|
# Value, used to select among various maps for the same IRI
|
1426
|
-
# @param [Boolean] vocab
|
1427
|
-
# specifies whether the passed iri should be compacted using the active context's vocabulary mapping
|
1428
1279
|
# @param [Boolean] reverse
|
1429
1280
|
# specifies whether a reverse property is being compacted
|
1430
|
-
# @param
|
1281
|
+
# @param [Boolean] vocab
|
1282
|
+
# specifies whether the passed iri should be compacted using the active context's vocabulary mapping
|
1431
1283
|
#
|
1432
1284
|
# @return [String] compacted form of IRI
|
1433
1285
|
# @see https://www.w3.org/TR/json-ld11-api/#iri-compaction
|
1434
|
-
def compact_iri(iri,
|
1286
|
+
def compact_iri(iri, base: nil, reverse: false, value: nil, vocab: nil)
|
1435
1287
|
return if iri.nil?
|
1436
1288
|
iri = iri.to_s
|
1437
1289
|
|
@@ -1536,7 +1388,7 @@ module JSON::LD
|
|
1536
1388
|
preferred_values = []
|
1537
1389
|
preferred_values << '@reverse' if tl_value == '@reverse'
|
1538
1390
|
if (tl_value == '@id' || tl_value == '@reverse') && value.is_a?(Hash) && value.has_key?('@id')
|
1539
|
-
t_iri = compact_iri(value['@id'], vocab: true,
|
1391
|
+
t_iri = compact_iri(value['@id'], vocab: true, base: base)
|
1540
1392
|
if (r_td = term_definitions[t_iri]) && r_td.id == value['@id']
|
1541
1393
|
preferred_values.concat(CONTAINERS_VOCAB_ID)
|
1542
1394
|
else
|
@@ -1602,7 +1454,7 @@ module JSON::LD
|
|
1602
1454
|
|
1603
1455
|
if !vocab
|
1604
1456
|
# transform iri to a relative IRI using the document's base IRI
|
1605
|
-
iri = remove_base(iri)
|
1457
|
+
iri = remove_base(self.base || base, iri)
|
1606
1458
|
return iri
|
1607
1459
|
else
|
1608
1460
|
return iri
|
@@ -1622,26 +1474,24 @@ module JSON::LD
|
|
1622
1474
|
# Value (literal or IRI) to be expanded
|
1623
1475
|
# @param [Boolean] useNativeTypes (false) use native representations
|
1624
1476
|
# @param [Boolean] rdfDirection (nil) decode i18n datatype if i18n-datatype
|
1477
|
+
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1625
1478
|
# @param [Hash{Symbol => Object}] options
|
1626
1479
|
#
|
1627
1480
|
# @return [Hash] Object representation of value
|
1628
1481
|
# @raise [RDF::ReaderError] if the iri cannot be expanded
|
1629
1482
|
# @see https://www.w3.org/TR/json-ld11-api/#value-expansion
|
1630
|
-
def expand_value(property, value, useNativeTypes: false, rdfDirection: nil, **options)
|
1631
|
-
#log_debug("expand_value") {"property: #{property.inspect}, value: #{value.inspect}"}
|
1632
|
-
|
1483
|
+
def expand_value(property, value, useNativeTypes: false, rdfDirection: nil, base: nil, **options)
|
1633
1484
|
td = term_definitions.fetch(property, TermDefinition.new(property))
|
1634
1485
|
|
1635
1486
|
# If the active property has a type mapping in active context that is @id, return a new JSON object containing a single key-value pair where the key is @id and the value is the result of using the IRI Expansion algorithm, passing active context, value, and true for document relative.
|
1636
1487
|
if value.is_a?(String) && td.type_mapping == '@id'
|
1637
1488
|
#log_debug("") {"as relative IRI: #{value.inspect}"}
|
1638
|
-
return {'@id' => expand_iri(value, documentRelative: true).to_s}
|
1489
|
+
return {'@id' => expand_iri(value, documentRelative: true, base: base).to_s}
|
1639
1490
|
end
|
1640
1491
|
|
1641
1492
|
# If active property has a type mapping in active context that is @vocab, return a new JSON object containing a single key-value pair where the key is @id and the value is the result of using the IRI Expansion algorithm, passing active context, value, true for vocab, and true for document relative.
|
1642
1493
|
if value.is_a?(String) && td.type_mapping == '@vocab'
|
1643
|
-
|
1644
|
-
return {'@id' => expand_iri(value, vocab: true, documentRelative: true).to_s}
|
1494
|
+
return {'@id' => expand_iri(value, vocab: true, documentRelative: true, base: base).to_s}
|
1645
1495
|
end
|
1646
1496
|
|
1647
1497
|
value = RDF::Literal(value) if
|
@@ -1651,16 +1501,14 @@ module JSON::LD
|
|
1651
1501
|
|
1652
1502
|
result = case value
|
1653
1503
|
when RDF::URI, RDF::Node
|
1654
|
-
#log_debug("URI | BNode") { value.to_s }
|
1655
1504
|
{'@id' => value.to_s}
|
1656
1505
|
when RDF::Literal
|
1657
|
-
#log_debug("Literal") {"datatype: #{value.datatype.inspect}"}
|
1658
1506
|
res = {}
|
1659
1507
|
if value.datatype == RDF::URI(RDF.to_uri + "JSON") && processingMode('json-ld-1.1')
|
1660
1508
|
# Value parsed as JSON
|
1661
1509
|
# FIXME: MultiJson
|
1662
|
-
res['@value'] = ::JSON.parse(value.object)
|
1663
1510
|
res['@type'] = '@json'
|
1511
|
+
res['@value'] = ::JSON.parse(value.object)
|
1664
1512
|
elsif value.datatype.start_with?("https://www.w3.org/ns/i18n#") && rdfDirection == 'i18n-datatype' && processingMode('json-ld-1.1')
|
1665
1513
|
lang, dir = value.datatype.fragment.split('_')
|
1666
1514
|
res['@value'] = value.to_s
|
@@ -1676,24 +1524,23 @@ module JSON::LD
|
|
1676
1524
|
end
|
1677
1525
|
res['@direction'] = dir
|
1678
1526
|
elsif useNativeTypes && RDF_LITERAL_NATIVE_TYPES.include?(value.datatype)
|
1679
|
-
res['@value'] = value.object
|
1680
1527
|
res['@type'] = uri(coerce(property)) if coerce(property)
|
1528
|
+
res['@value'] = value.object
|
1681
1529
|
else
|
1682
1530
|
value.canonicalize! if value.datatype == RDF::XSD.double
|
1683
|
-
res['@value'] = value.to_s
|
1684
1531
|
if coerce(property)
|
1685
1532
|
res['@type'] = uri(coerce(property)).to_s
|
1686
1533
|
elsif value.has_datatype?
|
1687
1534
|
res['@type'] = uri(value.datatype).to_s
|
1688
1535
|
elsif value.has_language? || language(property)
|
1689
1536
|
res['@language'] = (value.language || language(property)).to_s
|
1690
|
-
# FIXME: direction
|
1691
1537
|
end
|
1538
|
+
res['@value'] = value.to_s
|
1692
1539
|
end
|
1693
1540
|
res
|
1694
1541
|
else
|
1695
1542
|
# Otherwise, initialize result to a JSON object with an @value member whose value is set to value.
|
1696
|
-
res = {
|
1543
|
+
res = {}
|
1697
1544
|
|
1698
1545
|
if td.type_mapping && !CONTAINERS_ID_VOCAB.include?(td.type_mapping.to_s)
|
1699
1546
|
res['@type'] = td.type_mapping.to_s
|
@@ -1704,10 +1551,9 @@ module JSON::LD
|
|
1704
1551
|
res['@direction'] = direction if direction
|
1705
1552
|
end
|
1706
1553
|
|
1707
|
-
res
|
1554
|
+
res.merge('@value' => value)
|
1708
1555
|
end
|
1709
1556
|
|
1710
|
-
#log_debug("") {"=> #{result.inspect}"}
|
1711
1557
|
result
|
1712
1558
|
rescue ::JSON::ParserError => e
|
1713
1559
|
raise JSON::LD::JsonLdError::InvalidJsonLiteral, e.message
|
@@ -1720,13 +1566,13 @@ module JSON::LD
|
|
1720
1566
|
# Associated property used to find coercion rules
|
1721
1567
|
# @param [Hash] value
|
1722
1568
|
# Value (literal or IRI), in full object representation, to be compacted
|
1723
|
-
# @param
|
1569
|
+
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1724
1570
|
#
|
1725
1571
|
# @return [Hash] Object representation of value
|
1726
1572
|
# @raise [JsonLdError] if the iri cannot be expanded
|
1727
1573
|
# @see https://www.w3.org/TR/json-ld11-api/#value-compaction
|
1728
1574
|
# FIXME: revisit the specification version of this.
|
1729
|
-
def compact_value(property, value,
|
1575
|
+
def compact_value(property, value, base: nil)
|
1730
1576
|
#log_debug("compact_value") {"property: #{property.inspect}, value: #{value.inspect}"}
|
1731
1577
|
|
1732
1578
|
indexing = index?(value) && container(property).include?('@index')
|
@@ -1737,7 +1583,7 @@ module JSON::LD
|
|
1737
1583
|
when coerce(property) == '@id' && value.has_key?('@id') && (value.keys - %w(@id @index)).empty?
|
1738
1584
|
# Compact an @id coercion
|
1739
1585
|
#log_debug("") {" (@id & coerce)"}
|
1740
|
-
compact_iri(value['@id'])
|
1586
|
+
compact_iri(value['@id'], base: base)
|
1741
1587
|
when coerce(property) == '@vocab' && value.has_key?('@id') && (value.keys - %w(@id @index)).empty?
|
1742
1588
|
# Compact an @id coercion
|
1743
1589
|
#log_debug("") {" (@id & coerce & vocab)"}
|
@@ -1827,13 +1673,21 @@ module JSON::LD
|
|
1827
1673
|
v.join(" ") + "]"
|
1828
1674
|
end
|
1829
1675
|
|
1676
|
+
# Duplicate an active context, allowing it to be modified.
|
1830
1677
|
def dup
|
1831
|
-
# Also duplicate mappings, coerce and list
|
1832
1678
|
that = self
|
1833
|
-
ec =
|
1679
|
+
ec = Context.new(unfrozen: true, **@options)
|
1680
|
+
ec.context_base = that.context_base
|
1681
|
+
ec.base = that.base unless that.base.nil?
|
1682
|
+
ec.default_direction = that.default_direction
|
1683
|
+
ec.default_language = that.default_language
|
1684
|
+
ec.previous_context = that.previous_context
|
1685
|
+
ec.processingMode = that.processingMode if that.instance_variable_get(:@processingModee)
|
1686
|
+
ec.vocab = that.vocab if that.vocab
|
1687
|
+
|
1834
1688
|
ec.instance_eval do
|
1835
1689
|
@term_definitions = that.term_definitions.dup
|
1836
|
-
@iri_to_term = that.iri_to_term
|
1690
|
+
@iri_to_term = that.iri_to_term
|
1837
1691
|
end
|
1838
1692
|
ec
|
1839
1693
|
end
|
@@ -1879,20 +1733,11 @@ module JSON::LD
|
|
1879
1733
|
bnode(namer.get_sym($1))
|
1880
1734
|
else
|
1881
1735
|
value = RDF::URI(value)
|
1882
|
-
value.validate! if
|
1883
|
-
value.canonicalize! if @options[:canonicalize]
|
1884
|
-
value = RDF::URI.intern(value, {}) if @options[:intern]
|
1736
|
+
#value.validate! if options[:validate]
|
1885
1737
|
value
|
1886
1738
|
end
|
1887
1739
|
end
|
1888
1740
|
|
1889
|
-
# Clear the provided context, used for testing
|
1890
|
-
# @return [Context] self
|
1891
|
-
def clear_provided_context
|
1892
|
-
@provided_context = nil
|
1893
|
-
self
|
1894
|
-
end
|
1895
|
-
|
1896
1741
|
# Keep track of allocated BNodes
|
1897
1742
|
#
|
1898
1743
|
# Don't actually use the name provided, to prevent name alias issues.
|
@@ -1933,7 +1778,7 @@ module JSON::LD
|
|
1933
1778
|
# @return [Hash{String => Hash{String => String}}]
|
1934
1779
|
# @todo May want to include @set along with container to allow selecting terms using @set over those without @set. May require adding some notion of value cardinality to compact_iri
|
1935
1780
|
def inverse_context
|
1936
|
-
|
1781
|
+
Context.inverse_cache[self.hash] ||= begin
|
1937
1782
|
result = {}
|
1938
1783
|
default_language = (self.default_language || '@none').downcase
|
1939
1784
|
term_definitions.keys.sort do |a, b|
|
@@ -2028,10 +1873,11 @@ module JSON::LD
|
|
2028
1873
|
##
|
2029
1874
|
# Removes a base IRI from the given absolute IRI.
|
2030
1875
|
#
|
1876
|
+
# @param [String] base the base used for making `iri` relative
|
2031
1877
|
# @param [String] iri the absolute IRI
|
2032
1878
|
# @return [String]
|
2033
1879
|
# the relative IRI if relative to base, otherwise the absolute IRI.
|
2034
|
-
def remove_base(iri)
|
1880
|
+
def remove_base(base, iri)
|
2035
1881
|
return iri unless base
|
2036
1882
|
@base_and_parents ||= begin
|
2037
1883
|
u = base
|
@@ -2141,5 +1987,232 @@ module JSON::LD
|
|
2141
1987
|
end
|
2142
1988
|
Array(container)
|
2143
1989
|
end
|
1990
|
+
|
1991
|
+
# Term Definitions specify how properties and values have to be interpreted as well as the current vocabulary mapping and the default language
|
1992
|
+
class TermDefinition
|
1993
|
+
# @return [RDF::URI] IRI map
|
1994
|
+
attr_accessor :id
|
1995
|
+
|
1996
|
+
# @return [String] term name
|
1997
|
+
attr_accessor :term
|
1998
|
+
|
1999
|
+
# @return [String] Type mapping
|
2000
|
+
attr_accessor :type_mapping
|
2001
|
+
|
2002
|
+
# Base container mapping, without @set
|
2003
|
+
# @return [Array<'@index', '@language', '@index', '@set', '@type', '@id', '@graph'>] Container mapping
|
2004
|
+
attr_reader :container_mapping
|
2005
|
+
|
2006
|
+
# @return [String] Term used for nest properties
|
2007
|
+
attr_accessor :nest
|
2008
|
+
|
2009
|
+
# Language mapping of term, `false` is used if there is an explicit language mapping for this term.
|
2010
|
+
# @return [String] Language mapping
|
2011
|
+
attr_accessor :language_mapping
|
2012
|
+
|
2013
|
+
# Direction of term, `false` is used if there is explicit direction mapping mapping for this term.
|
2014
|
+
# @return ["ltr", "rtl"] direction_mapping
|
2015
|
+
attr_accessor :direction_mapping
|
2016
|
+
|
2017
|
+
# @return [Boolean] Reverse Property
|
2018
|
+
attr_accessor :reverse_property
|
2019
|
+
|
2020
|
+
# This is a simple term definition, not an expanded term definition
|
2021
|
+
# @return [Boolean]
|
2022
|
+
attr_accessor :simple
|
2023
|
+
|
2024
|
+
# Property used for data indexing; defaults to @index
|
2025
|
+
# @return [Boolean]
|
2026
|
+
attr_accessor :index
|
2027
|
+
|
2028
|
+
# Indicate that term may be used as a prefix
|
2029
|
+
attr_writer :prefix
|
2030
|
+
|
2031
|
+
# Term-specific context
|
2032
|
+
# @return [Hash{String => Object}]
|
2033
|
+
attr_accessor :context
|
2034
|
+
|
2035
|
+
# Term is protected.
|
2036
|
+
# @return [Boolean]
|
2037
|
+
attr_writer :protected
|
2038
|
+
|
2039
|
+
# This is a simple term definition, not an expanded term definition
|
2040
|
+
# @return [Boolean] simple
|
2041
|
+
def simple?; simple; end
|
2042
|
+
|
2043
|
+
# This is an appropriate term to use as the prefix of a compact IRI
|
2044
|
+
# @return [Boolean] simple
|
2045
|
+
def prefix?; @prefix; end
|
2046
|
+
|
2047
|
+
# Create a new Term Mapping with an ID
|
2048
|
+
# @param [String] term
|
2049
|
+
# @param [String] id
|
2050
|
+
# @param [String] type_mapping Type mapping
|
2051
|
+
# @param [Set<'@index', '@language', '@index', '@set', '@type', '@id', '@graph'>] container_mapping
|
2052
|
+
# @param [String] language_mapping
|
2053
|
+
# Language mapping of term, `false` is used if there is an explicit language mapping for this term
|
2054
|
+
# @param ["ltr", "rtl"] direction_mapping
|
2055
|
+
# Direction mapping of term, `false` is used if there is an explicit direction mapping for this term
|
2056
|
+
# @param [Boolean] reverse_property
|
2057
|
+
# @param [Boolean] protected mark resulting context as protected
|
2058
|
+
# @param [String] nest term used for nest properties
|
2059
|
+
# @param [Boolean] simple
|
2060
|
+
# This is a simple term definition, not an expanded term definition
|
2061
|
+
# @param [Boolean] prefix
|
2062
|
+
# Term may be used as a prefix
|
2063
|
+
def initialize(term,
|
2064
|
+
id: nil,
|
2065
|
+
index: nil,
|
2066
|
+
type_mapping: nil,
|
2067
|
+
container_mapping: nil,
|
2068
|
+
language_mapping: nil,
|
2069
|
+
direction_mapping: nil,
|
2070
|
+
reverse_property: false,
|
2071
|
+
nest: nil,
|
2072
|
+
protected: nil,
|
2073
|
+
simple: false,
|
2074
|
+
prefix: nil,
|
2075
|
+
context: nil)
|
2076
|
+
@term = term
|
2077
|
+
@id = id.to_s unless id.nil?
|
2078
|
+
@index = index.to_s unless index.nil?
|
2079
|
+
@type_mapping = type_mapping.to_s unless type_mapping.nil?
|
2080
|
+
self.container_mapping = container_mapping
|
2081
|
+
@language_mapping = language_mapping unless language_mapping.nil?
|
2082
|
+
@direction_mapping = direction_mapping unless direction_mapping.nil?
|
2083
|
+
@reverse_property = reverse_property
|
2084
|
+
@protected = protected
|
2085
|
+
@nest = nest unless nest.nil?
|
2086
|
+
@simple = simple
|
2087
|
+
@prefix = prefix unless prefix.nil?
|
2088
|
+
@context = context unless context.nil?
|
2089
|
+
end
|
2090
|
+
|
2091
|
+
# Term is protected.
|
2092
|
+
# @return [Boolean]
|
2093
|
+
def protected?; !!@protected; end
|
2094
|
+
|
2095
|
+
# Set container mapping, from an array which may include @set
|
2096
|
+
def container_mapping=(mapping)
|
2097
|
+
mapping = case mapping
|
2098
|
+
when Set then mapping
|
2099
|
+
when Array then Set.new(mapping)
|
2100
|
+
when String then Set[mapping]
|
2101
|
+
when nil then Set.new
|
2102
|
+
else
|
2103
|
+
raise "Shouldn't happen with #{mapping.inspect}"
|
2104
|
+
end
|
2105
|
+
if @as_set = mapping.include?('@set')
|
2106
|
+
mapping = mapping.dup
|
2107
|
+
mapping.delete('@set')
|
2108
|
+
end
|
2109
|
+
@container_mapping = mapping
|
2110
|
+
@index ||= '@index' if mapping.include?('@index')
|
2111
|
+
end
|
2112
|
+
|
2113
|
+
##
|
2114
|
+
# Output Hash or String definition for this definition considering @language and @vocab
|
2115
|
+
#
|
2116
|
+
# @param [Context] context
|
2117
|
+
# @return [String, Hash{String => Array[String], String}]
|
2118
|
+
def to_context_definition(context)
|
2119
|
+
cid = if context.vocab && id.start_with?(context.vocab)
|
2120
|
+
# Nothing to return unless it's the same as the vocab
|
2121
|
+
id == context.vocab ? context.vocab : id.to_s[context.vocab.length..-1]
|
2122
|
+
else
|
2123
|
+
# Find a term to act as a prefix
|
2124
|
+
iri, prefix = context.iri_to_term.detect {|i,p| id.to_s.start_with?(i.to_s)}
|
2125
|
+
iri && iri != id ? "#{prefix}:#{id.to_s[iri.length..-1]}" : id
|
2126
|
+
end
|
2127
|
+
|
2128
|
+
if simple?
|
2129
|
+
cid.to_s unless cid == term && context.vocab
|
2130
|
+
else
|
2131
|
+
defn = {}
|
2132
|
+
defn[reverse_property ? '@reverse' : '@id'] = cid.to_s unless cid == term && !reverse_property
|
2133
|
+
if type_mapping
|
2134
|
+
defn['@type'] = if KEYWORDS.include?(type_mapping)
|
2135
|
+
type_mapping
|
2136
|
+
else
|
2137
|
+
context.compact_iri(type_mapping, vocab: true)
|
2138
|
+
end
|
2139
|
+
end
|
2140
|
+
|
2141
|
+
cm = Array(container_mapping)
|
2142
|
+
cm << "@set" if as_set? && !cm.include?("@set")
|
2143
|
+
cm = cm.first if cm.length == 1
|
2144
|
+
defn['@container'] = cm unless cm.empty?
|
2145
|
+
# Language set as false to be output as null
|
2146
|
+
defn['@language'] = (@language_mapping ? @language_mapping : nil) unless @language_mapping.nil?
|
2147
|
+
defn['@direction'] = (@direction_mapping ? @direction_mapping : nil) unless @direction_mapping.nil?
|
2148
|
+
defn['@context'] = @context if @context
|
2149
|
+
defn['@nest'] = @nest if @nest
|
2150
|
+
defn['@index'] = @index if @index
|
2151
|
+
defn['@prefix'] = @prefix unless @prefix.nil?
|
2152
|
+
defn
|
2153
|
+
end
|
2154
|
+
end
|
2155
|
+
|
2156
|
+
##
|
2157
|
+
# Turn this into a source for a new instantiation
|
2158
|
+
# FIXME: context serialization
|
2159
|
+
# @return [String]
|
2160
|
+
def to_rb
|
2161
|
+
defn = [%(TermDefinition.new\(#{term.inspect})]
|
2162
|
+
%w(id index type_mapping container_mapping language_mapping direction_mapping reverse_property nest simple prefix context protected).each do |acc|
|
2163
|
+
v = instance_variable_get("@#{acc}".to_sym)
|
2164
|
+
v = v.to_s if v.is_a?(RDF::Term)
|
2165
|
+
if acc == 'container_mapping'
|
2166
|
+
v = v.to_a
|
2167
|
+
v << '@set' if as_set?
|
2168
|
+
v = v.first if v.length <= 1
|
2169
|
+
end
|
2170
|
+
defn << "#{acc}: #{v.inspect}" if v
|
2171
|
+
end
|
2172
|
+
defn.join(', ') + ")"
|
2173
|
+
end
|
2174
|
+
|
2175
|
+
# If container mapping was defined along with @set
|
2176
|
+
# @return [Boolean]
|
2177
|
+
def as_set?; @as_set || false; end
|
2178
|
+
|
2179
|
+
# Check if term definitions are identical, modulo @protected
|
2180
|
+
# @return [Boolean]
|
2181
|
+
def ==(other)
|
2182
|
+
other.is_a?(TermDefinition) &&
|
2183
|
+
id == other.id &&
|
2184
|
+
term == other.term &&
|
2185
|
+
type_mapping == other.type_mapping &&
|
2186
|
+
container_mapping == other.container_mapping &&
|
2187
|
+
nest == other.nest &&
|
2188
|
+
language_mapping == other.language_mapping &&
|
2189
|
+
direction_mapping == other.direction_mapping &&
|
2190
|
+
reverse_property == other.reverse_property &&
|
2191
|
+
simple == other.simple &&
|
2192
|
+
index == other.index &&
|
2193
|
+
context == other.context &&
|
2194
|
+
prefix? == other.prefix? &&
|
2195
|
+
as_set? == other.as_set?
|
2196
|
+
end
|
2197
|
+
|
2198
|
+
def inspect
|
2199
|
+
v = %w([TD)
|
2200
|
+
v << "id=#{@id}"
|
2201
|
+
v << "index=#{index.inspect}" unless index.nil?
|
2202
|
+
v << "term=#{@term}"
|
2203
|
+
v << "rev" if reverse_property
|
2204
|
+
v << "container=#{container_mapping}" if container_mapping
|
2205
|
+
v << "as_set=#{as_set?.inspect}"
|
2206
|
+
v << "lang=#{language_mapping.inspect}" unless language_mapping.nil?
|
2207
|
+
v << "dir=#{direction_mapping.inspect}" unless direction_mapping.nil?
|
2208
|
+
v << "type=#{type_mapping}" unless type_mapping.nil?
|
2209
|
+
v << "nest=#{nest.inspect}" unless nest.nil?
|
2210
|
+
v << "simple=true" if @simple
|
2211
|
+
v << "protected=true" if @protected
|
2212
|
+
v << "prefix=#{@prefix.inspect}" unless @prefix.nil?
|
2213
|
+
v << "has-context" unless context.nil?
|
2214
|
+
v.join(" ") + "]"
|
2215
|
+
end
|
2216
|
+
end
|
2144
2217
|
end
|
2145
2218
|
end
|