json-ld 3.1.1 → 3.1.6
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 +12 -7
- data/lib/json/ld/api.rb +41 -31
- data/lib/json/ld/compact.rb +58 -37
- data/lib/json/ld/conneg.rb +1 -1
- data/lib/json/ld/context.rb +641 -540
- data/lib/json/ld/expand.rb +160 -77
- data/lib/json/ld/flatten.rb +1 -1
- data/lib/json/ld/format.rb +20 -7
- data/lib/json/ld/frame.rb +0 -1
- data/lib/json/ld/from_rdf.rb +40 -17
- data/lib/json/ld/html/nokogiri.rb +2 -1
- data/lib/json/ld/html/rexml.rb +2 -1
- 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 +1 -0
- data/spec/context_spec.rb +20 -73
- data/spec/expand_spec.rb +277 -9
- data/spec/frame_spec.rb +44 -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_expand_spec.rb +2 -2
- data/spec/suite_frame_spec.rb +0 -1
- data/spec/suite_helper.rb +18 -8
- data/spec/suite_to_rdf_spec.rb +2 -1
- data/spec/to_rdf_spec.rb +206 -0
- metadata +65 -62
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
|
420
|
-
else
|
421
|
-
raise JsonLdError::InvalidDefaultLanguage, "@language must be a string: #{value.inspect}"
|
422
|
-
end
|
423
|
-
end
|
424
|
-
|
425
|
-
# @param [String] value
|
426
|
-
def default_direction=(value, **options)
|
427
|
-
@default_direction = if value
|
428
|
-
raise JsonLdError::InvalidBaseDirection, "@direction must be one or 'ltr', or 'rtl': #{value.inspect}" unless %w(ltr rtl).include?(value)
|
429
|
-
value
|
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
|
430
124
|
else
|
431
|
-
|
125
|
+
c.parse(local_context,
|
126
|
+
base: base,
|
127
|
+
override_protected: override_protected,
|
128
|
+
propagate: propagate)
|
432
129
|
end
|
433
130
|
end
|
434
131
|
|
435
132
|
##
|
436
|
-
#
|
437
|
-
#
|
438
|
-
# * With no arguments, retrieves the current set processingMode.
|
439
|
-
# * 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"
|
440
|
-
# * If expecting 1.1, and not set, it has the side-effect of setting mode to json-ld-1.1.
|
133
|
+
# Class-level cache used for retaining parsed remote contexts.
|
441
134
|
#
|
442
|
-
# @
|
443
|
-
# @
|
444
|
-
def
|
445
|
-
|
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
|
135
|
+
# @return [RDF::Util::Cache]
|
136
|
+
# @private
|
137
|
+
def self.cache
|
138
|
+
@cache ||= RDF::Util::Cache.new(CACHE_SIZE)
|
456
139
|
end
|
457
140
|
|
458
141
|
##
|
459
|
-
#
|
460
|
-
#
|
461
|
-
# * 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"
|
462
|
-
#
|
463
|
-
# 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.
|
464
|
-
# If processingMode has been set, and it is not "json-ld-1.1", a "processing mode conflict" has been detecting, and processing is aborted.
|
142
|
+
# Class-level cache inverse contexts.
|
465
143
|
#
|
466
|
-
# @
|
467
|
-
# @
|
468
|
-
|
469
|
-
|
470
|
-
value = "json-ld-1.1" if value == 1.1
|
471
|
-
case value
|
472
|
-
when "json-ld-1.0", "json-ld-1.1"
|
473
|
-
if @processingMode && @processingMode != value
|
474
|
-
raise JsonLdError::ProcessingModeConflict, "#{value} not compatible with #{@processingMode}"
|
475
|
-
end
|
476
|
-
@processingMode = value
|
477
|
-
else
|
478
|
-
raise JsonLdError::InvalidVersionValue, value.inspect
|
479
|
-
end
|
144
|
+
# @return [RDF::Util::Cache]
|
145
|
+
# @private
|
146
|
+
def self.inverse_cache
|
147
|
+
@inverse_cache ||= RDF::Util::Cache.new(CACHE_SIZE)
|
480
148
|
end
|
481
149
|
|
482
|
-
|
483
|
-
# @
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
150
|
+
##
|
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
|
493
170
|
end
|
494
|
-
v = expand_iri(value.to_s, vocab: true, documentRelative: true)
|
495
|
-
raise JsonLdError::InvalidVocabMapping, "@vocab must be an IRI: #{value.inspect}" if !v.valid? && @options[:validate]
|
496
|
-
v
|
497
|
-
when nil
|
498
|
-
nil
|
499
171
|
else
|
500
|
-
|
172
|
+
# Don't try to cache
|
173
|
+
context = JSON::LD::Context.allocate
|
174
|
+
context.send(:initialize, **options)
|
175
|
+
context
|
501
176
|
end
|
502
177
|
end
|
503
178
|
|
504
|
-
|
505
|
-
#
|
506
|
-
#
|
507
|
-
# @
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
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'
|
194
|
+
end
|
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")
|
201
|
+
|
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,18 +227,26 @@ 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 ([])
|
236
|
+
# @param [Boolean] validate_scoped (true).
|
237
|
+
# Validate scoped context, loading if necessary.
|
238
|
+
# If false, do not load scoped contexts.
|
529
239
|
# @raise [JsonLdError]
|
530
240
|
# on a remote context load error, syntax error, or a reference to a term which is not defined.
|
531
241
|
# @return [Context]
|
532
242
|
# @see https://www.w3.org/TR/json-ld11-api/index.html#context-processing-algorithm
|
533
|
-
def parse(local_context,
|
243
|
+
def parse(local_context,
|
244
|
+
base: nil,
|
245
|
+
override_protected: false,
|
246
|
+
propagate: true,
|
247
|
+
remote_contexts: [],
|
248
|
+
validate_scoped: true)
|
534
249
|
result = self.dup
|
535
|
-
result.provided_context = local_context if self.empty?
|
536
250
|
# Early check for @propagate, which can only appear in a local context
|
537
251
|
propagate = local_context.is_a?(Hash) ? local_context.fetch('@propagate', propagate) : propagate
|
538
252
|
result.previous_context ||= result.dup unless propagate
|
@@ -541,7 +255,7 @@ module JSON::LD
|
|
541
255
|
|
542
256
|
local_context.each do |context|
|
543
257
|
case context
|
544
|
-
when nil
|
258
|
+
when nil,false
|
545
259
|
# 3.1 If the `override_protected` is false, and the active context contains protected terms, an error is raised.
|
546
260
|
if override_protected || result.term_definitions.values.none?(&:protected?)
|
547
261
|
null_context = Context.new(**options)
|
@@ -553,37 +267,34 @@ module JSON::LD
|
|
553
267
|
end
|
554
268
|
when Context
|
555
269
|
#log_debug("parse") {"context: #{context.inspect}"}
|
556
|
-
result = context
|
270
|
+
result = result.merge(context)
|
557
271
|
when IO, StringIO
|
558
272
|
#log_debug("parse") {"io: #{context}"}
|
559
273
|
# Load context document, if it is an open file
|
560
274
|
begin
|
561
275
|
ctx = JSON.load(context)
|
562
276
|
raise JSON::LD::JsonLdError::InvalidRemoteContext, "Context missing @context key" if @options[:validate] && ctx['@context'].nil?
|
563
|
-
result = result.
|
564
|
-
result.provided_context = ctx["@context"] if [context] == local_context
|
565
|
-
result
|
277
|
+
result = result.parse(ctx["@context"] ? ctx["@context"] : {})
|
566
278
|
rescue JSON::ParserError => e
|
567
279
|
#log_debug("parse") {"Failed to parse @context from remote document at #{context}: #{e.message}"}
|
568
280
|
raise JSON::LD::JsonLdError::InvalidRemoteContext, "Failed to parse remote context at #{context}: #{e.message}" if @options[:validate]
|
569
|
-
self
|
281
|
+
self
|
570
282
|
end
|
571
283
|
when String, RDF::URI
|
572
284
|
#log_debug("parse") {"remote: #{context}, base: #{result.context_base || result.base}"}
|
573
285
|
|
574
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].
|
575
|
-
context = RDF::URI(result.context_base ||
|
287
|
+
context = RDF::URI(result.context_base || base).join(context)
|
576
288
|
context_canon = context.canonicalize
|
577
|
-
context_canon.scheme
|
289
|
+
context_canon.scheme = 'http' if context_canon.scheme == 'https'
|
290
|
+
|
291
|
+
# If validating a scoped context which has already been loaded, skip to the next one
|
292
|
+
next if !validate_scoped && remote_contexts.include?(context.to_s)
|
578
293
|
|
579
294
|
remote_contexts << context.to_s
|
580
295
|
raise JsonLdError::ContextOverflow, "#{context}" if remote_contexts.length >= MAX_CONTEXTS_LOADED
|
581
296
|
|
582
|
-
|
583
|
-
context_no_base.base = nil
|
584
|
-
context_no_base.context_base = context.to_s
|
585
|
-
|
586
|
-
if PRELOADED[context_canon.to_s]
|
297
|
+
cached_context = if PRELOADED[context_canon.to_s]
|
587
298
|
# If we have a cached context, merge it into the current context (result) and use as the new context
|
588
299
|
#log_debug("parse") {"=> cached_context: #{context_canon.to_s.inspect}"}
|
589
300
|
|
@@ -592,10 +303,10 @@ module JSON::LD
|
|
592
303
|
#log_debug("parse") {"=> (call)"}
|
593
304
|
PRELOADED[context_canon.to_s] = PRELOADED[context_canon.to_s].call
|
594
305
|
end
|
595
|
-
|
306
|
+
PRELOADED[context_canon.to_s]
|
596
307
|
else
|
597
308
|
# Load context document, if it is a string
|
598
|
-
begin
|
309
|
+
Context.cache[context_canon.to_s] ||= begin
|
599
310
|
context_opts = @options.merge(
|
600
311
|
profile: 'http://www.w3.org/ns/json-ld#context',
|
601
312
|
requestProfile: 'http://www.w3.org/ns/json-ld#context',
|
@@ -604,26 +315,30 @@ module JSON::LD
|
|
604
315
|
JSON::LD::API.loadRemoteDocument(context.to_s, **context_opts) do |remote_doc|
|
605
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.
|
606
317
|
raise JsonLdError::InvalidRemoteContext, "#{context}" unless remote_doc.document.is_a?(Hash) && remote_doc.document.has_key?('@context')
|
607
|
-
|
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
|
608
325
|
end
|
609
326
|
rescue JsonLdError::LoadingDocumentFailed => e
|
610
327
|
#log_debug("parse") {"Failed to retrieve @context from remote document at #{context_no_base.context_base.inspect}: #{e.message}"}
|
611
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{
|
328
|
+
raise JsonLdError::LoadingRemoteContextFailed, "#{context}: #{e.message}", e.backtrace
|
612
329
|
rescue JsonLdError
|
613
330
|
raise
|
614
331
|
rescue StandardError => e
|
615
332
|
#log_debug("parse") {"Failed to retrieve @context from remote document at #{context_no_base.context_base.inspect}: #{e.message}"}
|
616
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{
|
333
|
+
raise JsonLdError::LoadingRemoteContextFailed, "#{context}: #{e.message}", e.backtrace
|
617
334
|
end
|
618
|
-
|
619
|
-
# 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.
|
620
|
-
context = context_no_base.parse(context, remote_contexts: remote_contexts.dup, protected: protected, override_protected: override_protected, propagate: propagate)
|
621
|
-
PRELOADED[context_canon.to_s] = context.dup
|
622
|
-
context.provided_context = result.provided_context
|
623
335
|
end
|
624
|
-
|
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
|
625
341
|
result = context
|
626
|
-
#log_debug("parse") {"=> provided_context: #{context.inspect}"}
|
627
342
|
when Hash
|
628
343
|
context = context.dup # keep from modifying a hash passed as a param
|
629
344
|
|
@@ -642,31 +357,33 @@ module JSON::LD
|
|
642
357
|
# Retrieve remote context and merge the remaining context object into the result.
|
643
358
|
raise JsonLdError::InvalidContextEntry, "@import may only be used in 1.1 mode}" if result.processingMode("json-ld-1.0")
|
644
359
|
raise JsonLdError::InvalidImportValue, "@import must be a string: #{context['@import'].inspect}" unless context['@import'].is_a?(String)
|
645
|
-
|
360
|
+
import_loc = RDF::URI(result.context_base || base).join(context['@import'])
|
646
361
|
begin
|
647
362
|
context_opts = @options.merge(
|
648
363
|
profile: 'http://www.w3.org/ns/json-ld#context',
|
649
364
|
requestProfile: 'http://www.w3.org/ns/json-ld#context',
|
650
365
|
base: nil)
|
651
366
|
context_opts.delete(:headers)
|
652
|
-
|
653
|
-
|
654
|
-
|
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')
|
655
371
|
import_context = remote_doc.document['@context']
|
372
|
+
import_context.delete('@base')
|
656
373
|
raise JsonLdError::InvalidRemoteContext, "#{import_context.to_json} must be an object" unless import_context.is_a?(Hash)
|
657
374
|
raise JsonLdError::InvalidContextEntry, "#{import_context.to_json} must not include @import entry" if import_context.has_key?('@import')
|
658
375
|
context.delete(key)
|
659
376
|
context = import_context.merge(context)
|
660
377
|
end
|
661
378
|
rescue JsonLdError::LoadingDocumentFailed => e
|
662
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{
|
379
|
+
raise JsonLdError::LoadingRemoteContextFailed, "#{import_loc}: #{e.message}", e.backtrace
|
663
380
|
rescue JsonLdError
|
664
381
|
raise
|
665
382
|
rescue StandardError => e
|
666
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{
|
383
|
+
raise JsonLdError::LoadingRemoteContextFailed, "#{import_loc}: #{e.message}", e.backtrace
|
667
384
|
end
|
668
385
|
else
|
669
|
-
result.send(setter, context[key], remote_contexts: remote_contexts
|
386
|
+
result.send(setter, context[key], remote_contexts: remote_contexts)
|
670
387
|
end
|
671
388
|
context.delete(key)
|
672
389
|
end
|
@@ -677,8 +394,12 @@ module JSON::LD
|
|
677
394
|
context.each_key do |key|
|
678
395
|
# ... where key is not @base, @vocab, @language, or @version
|
679
396
|
result.create_term_definition(context, key, defined,
|
397
|
+
base: base,
|
680
398
|
override_protected: override_protected,
|
681
|
-
protected: context
|
399
|
+
protected: context['@protected'],
|
400
|
+
remote_contexts: remote_contexts.dup,
|
401
|
+
validate_scoped: validate_scoped
|
402
|
+
) unless NON_TERMDEF_KEYS.include?(key)
|
682
403
|
end
|
683
404
|
else
|
684
405
|
# 3.3) If context is not a JSON object, an invalid local context error has been detected and processing is aborted.
|
@@ -692,28 +413,29 @@ module JSON::LD
|
|
692
413
|
# Merge in a context, creating a new context with updates from `context`
|
693
414
|
#
|
694
415
|
# @param [Context] context
|
416
|
+
# @param [Boolean] override_protected Allow or disallow protected terms to be changed
|
695
417
|
# @return [Context]
|
696
|
-
def merge(context)
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
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
|
701
433
|
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
def merge!(context)
|
708
|
-
# FIXME: if new context removes the default language, this won't do anything
|
709
|
-
self.default_language = context.default_language if context.default_language
|
710
|
-
self.vocab = context.vocab if context.vocab
|
711
|
-
self.base = context.base if context.base
|
712
|
-
|
713
|
-
# Merge in Term Definitions
|
714
|
-
term_definitions.merge!(context.term_definitions)
|
715
|
-
@inverse_context = nil # Re-build after term definitions set
|
716
|
-
self
|
434
|
+
# Add term definitions
|
435
|
+
context.term_definitions.each do |term, definition|
|
436
|
+
ctx.term_definitions[term] = definition
|
437
|
+
end
|
438
|
+
ctx
|
717
439
|
end
|
718
440
|
|
719
441
|
# The following constants are used to reduce object allocations in #create_term_definition below
|
@@ -736,14 +458,22 @@ module JSON::LD
|
|
736
458
|
# @param [Hash] local_context
|
737
459
|
# @param [String] term
|
738
460
|
# @param [Hash] defined
|
461
|
+
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
739
462
|
# @param [Boolean] protected if true, causes all terms to be marked protected
|
740
463
|
# @param [Boolean] override_protected Protected terms may be cleared.
|
741
|
-
# @param [
|
742
|
-
#
|
464
|
+
# @param [Array<String>] remote_contexts
|
465
|
+
# @param [Boolean] validate_scoped (true).
|
466
|
+
# Validate scoped context, loading if necessary.
|
467
|
+
# If false, do not load scoped contexts.
|
743
468
|
# @raise [JsonLdError]
|
744
469
|
# Represents a cyclical term dependency
|
745
470
|
# @see https://www.w3.org/TR/json-ld11-api/index.html#create-term-definition
|
746
|
-
def create_term_definition(local_context, term, defined,
|
471
|
+
def create_term_definition(local_context, term, defined,
|
472
|
+
base: nil,
|
473
|
+
override_protected: false,
|
474
|
+
protected: nil,
|
475
|
+
remote_contexts: [],
|
476
|
+
validate_scoped: true)
|
747
477
|
# Expand a string value, unless it matches a keyword
|
748
478
|
#log_debug("create_term_definition") {"term = #{term.inspect}"}
|
749
479
|
|
@@ -763,6 +493,7 @@ module JSON::LD
|
|
763
493
|
# Since keywords cannot be overridden, term must not be a keyword. Otherwise, an invalid value has been detected, which is an error.
|
764
494
|
if term == '@type' &&
|
765
495
|
value.is_a?(Hash) &&
|
496
|
+
!value.empty? &&
|
766
497
|
processingMode("json-ld-1.1") &&
|
767
498
|
(value.keys - %w(@container @protected)).empty? &&
|
768
499
|
value.fetch('@container', '@set') == '@set'
|
@@ -841,6 +572,11 @@ module JSON::LD
|
|
841
572
|
raise JsonLdError::InvalidIRIMapping, "expected value of @reverse to be a string: #{value['@reverse'].inspect} on term #{term.inspect}" unless
|
842
573
|
value['@reverse'].is_a?(String)
|
843
574
|
|
575
|
+
if value['@reverse'].to_s.match?(/^@[a-zA-Z]+$/) && @options[:validate]
|
576
|
+
warn "Values beginning with '@' are reserved for future use and ignored: #{value['@reverse']}."
|
577
|
+
return
|
578
|
+
end
|
579
|
+
|
844
580
|
# Otherwise, set the IRI mapping of definition to the result of using the IRI Expansion algorithm, passing active context, the value associated with the @reverse key for value, true for vocab, true for document relative, local context, and defined. If the result is not an absolute IRI, i.e., it contains no colon (:), an invalid IRI mapping error has been detected and processing is aborted.
|
845
581
|
definition.id = expand_iri(value['@reverse'],
|
846
582
|
vocab: true,
|
@@ -849,11 +585,6 @@ module JSON::LD
|
|
849
585
|
raise JsonLdError::InvalidIRIMapping, "non-absolute @reverse IRI: #{definition.id} on term #{term.inspect}" unless
|
850
586
|
definition.id.is_a?(RDF::Node) || definition.id.is_a?(RDF::URI) && definition.id.absolute?
|
851
587
|
|
852
|
-
if value['@reverse'].to_s.match?(/^@[a-zA-Z]+$/) && @options[:validate]
|
853
|
-
warn "Values beginning with '@' are reserved for future use and ignored: #{value['@reverse']}."
|
854
|
-
return
|
855
|
-
end
|
856
|
-
|
857
588
|
if term[1..-1].to_s.include?(':') && (term_iri = expand_iri(term)) != definition.id
|
858
589
|
raise JsonLdError::InvalidIRIMapping, "term #{term} expands to #{definition.id}, not #{term_iri}"
|
859
590
|
end
|
@@ -957,9 +688,17 @@ module JSON::LD
|
|
957
688
|
|
958
689
|
if value.has_key?('@context')
|
959
690
|
begin
|
960
|
-
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)
|
961
696
|
# Record null context in array form
|
962
|
-
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
|
963
702
|
rescue JsonLdError => e
|
964
703
|
raise JsonLdError::InvalidScopedContext, "Term definition for #{term.inspect} contains illegal value for @context: #{e.message}"
|
965
704
|
end
|
@@ -1017,9 +756,130 @@ module JSON::LD
|
|
1017
756
|
|
1018
757
|
term_definitions[term] = definition
|
1019
758
|
defined[term] = true
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
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
|
1023
883
|
end
|
1024
884
|
|
1025
885
|
##
|
@@ -1028,40 +888,44 @@ module JSON::LD
|
|
1028
888
|
# If a context was supplied in global options, use that, otherwise, generate one
|
1029
889
|
# from this representation.
|
1030
890
|
#
|
891
|
+
# @param [Array, Hash, Context, IO, StringIO] provided_context (nil)
|
892
|
+
# Original context to use, if available
|
1031
893
|
# @param [Hash{Symbol => Object}] options ({})
|
1032
894
|
# @return [Hash]
|
1033
|
-
def serialize(**options)
|
1034
|
-
#
|
895
|
+
def serialize(provided_context: nil, **options)
|
896
|
+
#log_debug("serlialize: generate context")
|
897
|
+
#log_debug("") {"=> context: #{inspect}"}
|
1035
898
|
use_context = case provided_context
|
1036
899
|
when String, RDF::URI
|
1037
900
|
#log_debug "serlialize: reuse context: #{provided_context.inspect}"
|
1038
901
|
provided_context.to_s
|
1039
|
-
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
|
1040
907
|
#log_debug "serlialize: reuse context: #{provided_context.inspect}"
|
1041
908
|
provided_context
|
909
|
+
when IO, StringIO
|
910
|
+
provided_context.rewind
|
911
|
+
JSON.load(provided_context).fetch('@context', {})
|
1042
912
|
else
|
1043
|
-
#log_debug("serlialize: generate context")
|
1044
|
-
#log_debug("") {"=> context: #{inspect}"}
|
1045
913
|
ctx = {}
|
1046
|
-
ctx['@
|
914
|
+
ctx['@version'] = 1.1 if @processingMode == 'json-ld-1.1'
|
915
|
+
ctx['@base'] = base.to_s if base
|
1047
916
|
ctx['@direction'] = default_direction.to_s if default_direction
|
1048
917
|
ctx['@language'] = default_language.to_s if default_language
|
1049
918
|
ctx['@vocab'] = vocab.to_s if vocab
|
1050
919
|
|
1051
920
|
# Term Definitions
|
1052
|
-
term_definitions.
|
1053
|
-
|
1054
|
-
ctx[term] = defn if defn
|
921
|
+
term_definitions.each do |term, defn|
|
922
|
+
ctx[term] = defn.to_context_definition(self)
|
1055
923
|
end
|
1056
|
-
|
1057
|
-
#log_debug("") {"start_doc: context=#{ctx.inspect}"}
|
1058
924
|
ctx
|
1059
925
|
end
|
1060
926
|
|
1061
927
|
# Return hash with @context, or empty
|
1062
|
-
|
1063
|
-
r['@context'] = use_context unless use_context.nil? || use_context.empty?
|
1064
|
-
r
|
928
|
+
use_context.nil? || use_context.empty? ? {} : {'@context' => use_context}
|
1065
929
|
end
|
1066
930
|
|
1067
931
|
##
|
@@ -1228,7 +1092,7 @@ module JSON::LD
|
|
1228
1092
|
term.nest
|
1229
1093
|
else
|
1230
1094
|
nest_term = find_definition(term.nest)
|
1231
|
-
raise JsonLdError::InvalidNestValue, "nest must a term resolving to @nest, was #{nest_term.inspect}" unless nest_term && nest_term.
|
1095
|
+
raise JsonLdError::InvalidNestValue, "nest must a term resolving to @nest, was #{nest_term.inspect}" unless nest_term && nest_term.id == '@nest'
|
1232
1096
|
term.nest
|
1233
1097
|
end
|
1234
1098
|
end
|
@@ -1289,26 +1153,27 @@ module JSON::LD
|
|
1289
1153
|
#
|
1290
1154
|
# @param [String] value
|
1291
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.
|
1292
1160
|
# @param [Boolean] documentRelative (false)
|
1293
|
-
# @param [Boolean] vocab (false)
|
1294
1161
|
# @param [Hash] local_context
|
1295
1162
|
# Used during Context Processing.
|
1296
|
-
# @param [
|
1297
|
-
# Used during Context Processing.
|
1298
|
-
# @param [Boolean] as_string (false) transform RDF::Resource values to string
|
1163
|
+
# @param [Boolean] vocab (false)
|
1299
1164
|
# @param [Hash{Symbol => Object}] options
|
1300
1165
|
# @return [RDF::Resource, String]
|
1301
1166
|
# IRI or String, if it's a keyword
|
1302
1167
|
# @raise [JSON::LD::JsonLdError::InvalidIRIMapping] if the value cannot be expanded
|
1303
1168
|
# @see https://www.w3.org/TR/json-ld11-api/#iri-expansion
|
1304
1169
|
def expand_iri(value,
|
1170
|
+
as_string: false,
|
1171
|
+
base: nil,
|
1172
|
+
defined: nil,
|
1305
1173
|
documentRelative: false,
|
1306
|
-
vocab: false,
|
1307
1174
|
local_context: nil,
|
1308
|
-
|
1309
|
-
|
1310
|
-
**options
|
1311
|
-
)
|
1175
|
+
vocab: false,
|
1176
|
+
**options)
|
1312
1177
|
return (value && as_string ? value.to_s : value) unless value.is_a?(String)
|
1313
1178
|
|
1314
1179
|
return value if KEYWORDS.include?(value)
|
@@ -1328,7 +1193,8 @@ module JSON::LD
|
|
1328
1193
|
# If active context has a term definition for value, and the associated mapping is a keyword, return that keyword.
|
1329
1194
|
# If vocab is true and the active context has a term definition for value, return the associated IRI mapping.
|
1330
1195
|
if (v_td = term_definitions[value]) && (vocab || KEYWORDS.include?(v_td.id))
|
1331
|
-
|
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)
|
1332
1198
|
end
|
1333
1199
|
|
1334
1200
|
# If value contains a colon (:), it is either an absolute IRI or a compact IRI:
|
@@ -1361,18 +1227,32 @@ module JSON::LD
|
|
1361
1227
|
end
|
1362
1228
|
end
|
1363
1229
|
|
1230
|
+
iri = value.is_a?(RDF::URI) ? value : RDF::URI(value)
|
1364
1231
|
result = if vocab && self.vocab
|
1365
1232
|
# If vocab is true, and active context has a vocabulary mapping, return the result of concatenating the vocabulary mapping with value.
|
1366
|
-
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
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?
|
1372
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.
|
1373
1253
|
raise JSON::LD::JsonLdError::InvalidIRIMapping, "not an absolute IRI: #{value}"
|
1374
1254
|
else
|
1375
|
-
|
1255
|
+
iri
|
1376
1256
|
end
|
1377
1257
|
result && as_string ? result.to_s : result
|
1378
1258
|
end
|
@@ -1393,17 +1273,17 @@ module JSON::LD
|
|
1393
1273
|
# Compacts an absolute IRI to the shortest matching term or compact IRI
|
1394
1274
|
#
|
1395
1275
|
# @param [RDF::URI] iri
|
1276
|
+
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1396
1277
|
# @param [Object] value
|
1397
1278
|
# Value, used to select among various maps for the same IRI
|
1398
|
-
# @param [Boolean] vocab
|
1399
|
-
# specifies whether the passed iri should be compacted using the active context's vocabulary mapping
|
1400
1279
|
# @param [Boolean] reverse
|
1401
1280
|
# specifies whether a reverse property is being compacted
|
1402
|
-
# @param
|
1281
|
+
# @param [Boolean] vocab
|
1282
|
+
# specifies whether the passed iri should be compacted using the active context's vocabulary mapping
|
1403
1283
|
#
|
1404
1284
|
# @return [String] compacted form of IRI
|
1405
1285
|
# @see https://www.w3.org/TR/json-ld11-api/#iri-compaction
|
1406
|
-
def compact_iri(iri,
|
1286
|
+
def compact_iri(iri, base: nil, reverse: false, value: nil, vocab: nil)
|
1407
1287
|
return if iri.nil?
|
1408
1288
|
iri = iri.to_s
|
1409
1289
|
|
@@ -1508,7 +1388,7 @@ module JSON::LD
|
|
1508
1388
|
preferred_values = []
|
1509
1389
|
preferred_values << '@reverse' if tl_value == '@reverse'
|
1510
1390
|
if (tl_value == '@id' || tl_value == '@reverse') && value.is_a?(Hash) && value.has_key?('@id')
|
1511
|
-
t_iri = compact_iri(value['@id'], vocab: true,
|
1391
|
+
t_iri = compact_iri(value['@id'], vocab: true, base: base)
|
1512
1392
|
if (r_td = term_definitions[t_iri]) && r_td.id == value['@id']
|
1513
1393
|
preferred_values.concat(CONTAINERS_VOCAB_ID)
|
1514
1394
|
else
|
@@ -1574,7 +1454,7 @@ module JSON::LD
|
|
1574
1454
|
|
1575
1455
|
if !vocab
|
1576
1456
|
# transform iri to a relative IRI using the document's base IRI
|
1577
|
-
iri = remove_base(iri)
|
1457
|
+
iri = remove_base(self.base || base, iri)
|
1578
1458
|
return iri
|
1579
1459
|
else
|
1580
1460
|
return iri
|
@@ -1594,26 +1474,24 @@ module JSON::LD
|
|
1594
1474
|
# Value (literal or IRI) to be expanded
|
1595
1475
|
# @param [Boolean] useNativeTypes (false) use native representations
|
1596
1476
|
# @param [Boolean] rdfDirection (nil) decode i18n datatype if i18n-datatype
|
1477
|
+
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1597
1478
|
# @param [Hash{Symbol => Object}] options
|
1598
1479
|
#
|
1599
1480
|
# @return [Hash] Object representation of value
|
1600
1481
|
# @raise [RDF::ReaderError] if the iri cannot be expanded
|
1601
1482
|
# @see https://www.w3.org/TR/json-ld11-api/#value-expansion
|
1602
|
-
def expand_value(property, value, useNativeTypes: false, rdfDirection: nil, **options)
|
1603
|
-
#log_debug("expand_value") {"property: #{property.inspect}, value: #{value.inspect}"}
|
1604
|
-
|
1483
|
+
def expand_value(property, value, useNativeTypes: false, rdfDirection: nil, base: nil, **options)
|
1605
1484
|
td = term_definitions.fetch(property, TermDefinition.new(property))
|
1606
1485
|
|
1607
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.
|
1608
1487
|
if value.is_a?(String) && td.type_mapping == '@id'
|
1609
1488
|
#log_debug("") {"as relative IRI: #{value.inspect}"}
|
1610
|
-
return {'@id' => expand_iri(value, documentRelative: true).to_s}
|
1489
|
+
return {'@id' => expand_iri(value, documentRelative: true, base: base).to_s}
|
1611
1490
|
end
|
1612
1491
|
|
1613
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.
|
1614
1493
|
if value.is_a?(String) && td.type_mapping == '@vocab'
|
1615
|
-
|
1616
|
-
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}
|
1617
1495
|
end
|
1618
1496
|
|
1619
1497
|
value = RDF::Literal(value) if
|
@@ -1623,16 +1501,14 @@ module JSON::LD
|
|
1623
1501
|
|
1624
1502
|
result = case value
|
1625
1503
|
when RDF::URI, RDF::Node
|
1626
|
-
#log_debug("URI | BNode") { value.to_s }
|
1627
1504
|
{'@id' => value.to_s}
|
1628
1505
|
when RDF::Literal
|
1629
|
-
#log_debug("Literal") {"datatype: #{value.datatype.inspect}"}
|
1630
1506
|
res = {}
|
1631
1507
|
if value.datatype == RDF::URI(RDF.to_uri + "JSON") && processingMode('json-ld-1.1')
|
1632
1508
|
# Value parsed as JSON
|
1633
1509
|
# FIXME: MultiJson
|
1634
|
-
res['@value'] = ::JSON.parse(value.object)
|
1635
1510
|
res['@type'] = '@json'
|
1511
|
+
res['@value'] = ::JSON.parse(value.object)
|
1636
1512
|
elsif value.datatype.start_with?("https://www.w3.org/ns/i18n#") && rdfDirection == 'i18n-datatype' && processingMode('json-ld-1.1')
|
1637
1513
|
lang, dir = value.datatype.fragment.split('_')
|
1638
1514
|
res['@value'] = value.to_s
|
@@ -1648,24 +1524,23 @@ module JSON::LD
|
|
1648
1524
|
end
|
1649
1525
|
res['@direction'] = dir
|
1650
1526
|
elsif useNativeTypes && RDF_LITERAL_NATIVE_TYPES.include?(value.datatype)
|
1651
|
-
res['@value'] = value.object
|
1652
1527
|
res['@type'] = uri(coerce(property)) if coerce(property)
|
1528
|
+
res['@value'] = value.object
|
1653
1529
|
else
|
1654
1530
|
value.canonicalize! if value.datatype == RDF::XSD.double
|
1655
|
-
res['@value'] = value.to_s
|
1656
1531
|
if coerce(property)
|
1657
1532
|
res['@type'] = uri(coerce(property)).to_s
|
1658
1533
|
elsif value.has_datatype?
|
1659
1534
|
res['@type'] = uri(value.datatype).to_s
|
1660
1535
|
elsif value.has_language? || language(property)
|
1661
1536
|
res['@language'] = (value.language || language(property)).to_s
|
1662
|
-
# FIXME: direction
|
1663
1537
|
end
|
1538
|
+
res['@value'] = value.to_s
|
1664
1539
|
end
|
1665
1540
|
res
|
1666
1541
|
else
|
1667
1542
|
# Otherwise, initialize result to a JSON object with an @value member whose value is set to value.
|
1668
|
-
res = {
|
1543
|
+
res = {}
|
1669
1544
|
|
1670
1545
|
if td.type_mapping && !CONTAINERS_ID_VOCAB.include?(td.type_mapping.to_s)
|
1671
1546
|
res['@type'] = td.type_mapping.to_s
|
@@ -1676,10 +1551,9 @@ module JSON::LD
|
|
1676
1551
|
res['@direction'] = direction if direction
|
1677
1552
|
end
|
1678
1553
|
|
1679
|
-
res
|
1554
|
+
res.merge('@value' => value)
|
1680
1555
|
end
|
1681
1556
|
|
1682
|
-
#log_debug("") {"=> #{result.inspect}"}
|
1683
1557
|
result
|
1684
1558
|
rescue ::JSON::ParserError => e
|
1685
1559
|
raise JSON::LD::JsonLdError::InvalidJsonLiteral, e.message
|
@@ -1692,13 +1566,13 @@ module JSON::LD
|
|
1692
1566
|
# Associated property used to find coercion rules
|
1693
1567
|
# @param [Hash] value
|
1694
1568
|
# Value (literal or IRI), in full object representation, to be compacted
|
1695
|
-
# @param
|
1569
|
+
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1696
1570
|
#
|
1697
1571
|
# @return [Hash] Object representation of value
|
1698
1572
|
# @raise [JsonLdError] if the iri cannot be expanded
|
1699
1573
|
# @see https://www.w3.org/TR/json-ld11-api/#value-compaction
|
1700
1574
|
# FIXME: revisit the specification version of this.
|
1701
|
-
def compact_value(property, value,
|
1575
|
+
def compact_value(property, value, base: nil)
|
1702
1576
|
#log_debug("compact_value") {"property: #{property.inspect}, value: #{value.inspect}"}
|
1703
1577
|
|
1704
1578
|
indexing = index?(value) && container(property).include?('@index')
|
@@ -1709,7 +1583,7 @@ module JSON::LD
|
|
1709
1583
|
when coerce(property) == '@id' && value.has_key?('@id') && (value.keys - %w(@id @index)).empty?
|
1710
1584
|
# Compact an @id coercion
|
1711
1585
|
#log_debug("") {" (@id & coerce)"}
|
1712
|
-
compact_iri(value['@id'])
|
1586
|
+
compact_iri(value['@id'], base: base)
|
1713
1587
|
when coerce(property) == '@vocab' && value.has_key?('@id') && (value.keys - %w(@id @index)).empty?
|
1714
1588
|
# Compact an @id coercion
|
1715
1589
|
#log_debug("") {" (@id & coerce & vocab)"}
|
@@ -1799,13 +1673,21 @@ module JSON::LD
|
|
1799
1673
|
v.join(" ") + "]"
|
1800
1674
|
end
|
1801
1675
|
|
1676
|
+
# Duplicate an active context, allowing it to be modified.
|
1802
1677
|
def dup
|
1803
|
-
# Also duplicate mappings, coerce and list
|
1804
1678
|
that = self
|
1805
|
-
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
|
+
|
1806
1688
|
ec.instance_eval do
|
1807
1689
|
@term_definitions = that.term_definitions.dup
|
1808
|
-
@iri_to_term = that.iri_to_term
|
1690
|
+
@iri_to_term = that.iri_to_term
|
1809
1691
|
end
|
1810
1692
|
ec
|
1811
1693
|
end
|
@@ -1819,7 +1701,7 @@ module JSON::LD
|
|
1819
1701
|
# @param [String] term
|
1820
1702
|
# @return [Boolean]
|
1821
1703
|
def term_valid?(term)
|
1822
|
-
term.is_a?(String)
|
1704
|
+
term.is_a?(String) && !term.empty?
|
1823
1705
|
end
|
1824
1706
|
|
1825
1707
|
# Reverse term mapping, typically used for finding aliases for keys.
|
@@ -1851,20 +1733,11 @@ module JSON::LD
|
|
1851
1733
|
bnode(namer.get_sym($1))
|
1852
1734
|
else
|
1853
1735
|
value = RDF::URI(value)
|
1854
|
-
value.validate! if
|
1855
|
-
value.canonicalize! if @options[:canonicalize]
|
1856
|
-
value = RDF::URI.intern(value, {}) if @options[:intern]
|
1736
|
+
#value.validate! if options[:validate]
|
1857
1737
|
value
|
1858
1738
|
end
|
1859
1739
|
end
|
1860
1740
|
|
1861
|
-
# Clear the provided context, used for testing
|
1862
|
-
# @return [Context] self
|
1863
|
-
def clear_provided_context
|
1864
|
-
@provided_context = nil
|
1865
|
-
self
|
1866
|
-
end
|
1867
|
-
|
1868
1741
|
# Keep track of allocated BNodes
|
1869
1742
|
#
|
1870
1743
|
# Don't actually use the name provided, to prevent name alias issues.
|
@@ -1905,7 +1778,7 @@ module JSON::LD
|
|
1905
1778
|
# @return [Hash{String => Hash{String => String}}]
|
1906
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
|
1907
1780
|
def inverse_context
|
1908
|
-
|
1781
|
+
Context.inverse_cache[self.hash] ||= begin
|
1909
1782
|
result = {}
|
1910
1783
|
default_language = (self.default_language || '@none').downcase
|
1911
1784
|
term_definitions.keys.sort do |a, b|
|
@@ -2000,10 +1873,11 @@ module JSON::LD
|
|
2000
1873
|
##
|
2001
1874
|
# Removes a base IRI from the given absolute IRI.
|
2002
1875
|
#
|
1876
|
+
# @param [String] base the base used for making `iri` relative
|
2003
1877
|
# @param [String] iri the absolute IRI
|
2004
1878
|
# @return [String]
|
2005
1879
|
# the relative IRI if relative to base, otherwise the absolute IRI.
|
2006
|
-
def remove_base(iri)
|
1880
|
+
def remove_base(base, iri)
|
2007
1881
|
return iri unless base
|
2008
1882
|
@base_and_parents ||= begin
|
2009
1883
|
u = base
|
@@ -2113,5 +1987,232 @@ module JSON::LD
|
|
2113
1987
|
end
|
2114
1988
|
Array(container)
|
2115
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
|
2116
2217
|
end
|
2117
2218
|
end
|