json-ld 3.1.2 → 3.1.7
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.
- 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
|