json-ld 3.1.3 → 3.1.8
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 +138 -49
- data/VERSION +1 -1
- data/bin/jsonld +28 -31
- data/lib/json/ld.rb +8 -2
- data/lib/json/ld/api.rb +55 -38
- data/lib/json/ld/compact.rb +68 -40
- data/lib/json/ld/conneg.rb +1 -1
- data/lib/json/ld/context.rb +570 -521
- data/lib/json/ld/expand.rb +203 -84
- data/lib/json/ld/extensions.rb +4 -4
- data/lib/json/ld/flatten.rb +92 -9
- data/lib/json/ld/format.rb +21 -8
- data/lib/json/ld/frame.rb +8 -8
- data/lib/json/ld/from_rdf.rb +42 -19
- data/lib/json/ld/reader.rb +21 -11
- data/lib/json/ld/streaming_reader.rb +578 -0
- data/lib/json/ld/streaming_writer.rb +4 -4
- data/lib/json/ld/to_rdf.rb +11 -7
- data/lib/json/ld/utils.rb +13 -13
- data/lib/json/ld/writer.rb +12 -5
- data/spec/api_spec.rb +1 -1
- data/spec/compact_spec.rb +207 -3
- data/spec/context_spec.rb +4 -42
- data/spec/expand_spec.rb +631 -0
- data/spec/flatten_spec.rb +517 -1
- data/spec/from_rdf_spec.rb +181 -0
- data/spec/matchers.rb +1 -1
- data/spec/rdfstar_spec.rb +25 -0
- data/spec/reader_spec.rb +33 -34
- data/spec/spec_helper.rb +33 -0
- data/spec/streaming_reader_spec.rb +237 -0
- data/spec/suite_flatten_spec.rb +4 -0
- data/spec/suite_frame_spec.rb +7 -0
- data/spec/suite_helper.rb +25 -13
- data/spec/suite_to_rdf_spec.rb +1 -0
- data/spec/to_rdf_spec.rb +209 -3
- data/spec/writer_spec.rb +193 -0
- metadata +68 -63
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,10 +23,13 @@ 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
|
+
|
24
29
|
##
|
25
30
|
# Defines the maximum number of interned URI references that can be held
|
26
31
|
# cached in memory at any one time.
|
27
|
-
CACHE_SIZE =
|
32
|
+
CACHE_SIZE = 100 # unlimited by default
|
28
33
|
|
29
34
|
class << self
|
30
35
|
##
|
@@ -45,242 +50,11 @@ module JSON::LD
|
|
45
50
|
end
|
46
51
|
end
|
47
52
|
|
48
|
-
# Term Definitions specify how properties and values have to be interpreted as well as the current vocabulary mapping and the default language
|
49
|
-
class TermDefinition
|
50
|
-
# @return [RDF::URI] IRI map
|
51
|
-
attr_accessor :id
|
52
|
-
|
53
|
-
# @return [String] term name
|
54
|
-
attr_accessor :term
|
55
|
-
|
56
|
-
# @return [String] Type mapping
|
57
|
-
attr_accessor :type_mapping
|
58
|
-
|
59
|
-
# Base container mapping, without @set
|
60
|
-
# @return [Array<'@index', '@language', '@index', '@set', '@type', '@id', '@graph'>] Container mapping
|
61
|
-
attr_reader :container_mapping
|
62
|
-
|
63
|
-
# @return [String] Term used for nest properties
|
64
|
-
attr_accessor :nest
|
65
|
-
|
66
|
-
# Language mapping of term, `false` is used if there is an explicit language mapping for this term.
|
67
|
-
# @return [String] Language mapping
|
68
|
-
attr_accessor :language_mapping
|
69
|
-
|
70
|
-
# Direction of term, `false` is used if there is explicit direction mapping mapping for this term.
|
71
|
-
# @return ["ltr", "rtl"] direction_mapping
|
72
|
-
attr_accessor :direction_mapping
|
73
|
-
|
74
|
-
# @return [Boolean] Reverse Property
|
75
|
-
attr_accessor :reverse_property
|
76
|
-
|
77
|
-
# This is a simple term definition, not an expanded term definition
|
78
|
-
# @return [Boolean]
|
79
|
-
attr_accessor :simple
|
80
|
-
|
81
|
-
# Property used for data indexing; defaults to @index
|
82
|
-
# @return [Boolean]
|
83
|
-
attr_accessor :index
|
84
|
-
|
85
|
-
# Indicate that term may be used as a prefix
|
86
|
-
attr_writer :prefix
|
87
|
-
|
88
|
-
# Term-specific context
|
89
|
-
# @return [Hash{String => Object}]
|
90
|
-
attr_accessor :context
|
91
|
-
|
92
|
-
# Term is protected.
|
93
|
-
# @return [Boolean]
|
94
|
-
attr_writer :protected
|
95
|
-
|
96
|
-
# This is a simple term definition, not an expanded term definition
|
97
|
-
# @return [Boolean] simple
|
98
|
-
def simple?; simple; end
|
99
|
-
|
100
|
-
# This is an appropriate term to use as the prefix of a compact IRI
|
101
|
-
# @return [Boolean] simple
|
102
|
-
def prefix?; @prefix; end
|
103
|
-
|
104
|
-
# Create a new Term Mapping with an ID
|
105
|
-
# @param [String] term
|
106
|
-
# @param [String] id
|
107
|
-
# @param [String] type_mapping Type mapping
|
108
|
-
# @param [Set<'@index', '@language', '@index', '@set', '@type', '@id', '@graph'>] container_mapping
|
109
|
-
# @param [String] language_mapping
|
110
|
-
# Language mapping of term, `false` is used if there is an explicit language mapping for this term
|
111
|
-
# @param ["ltr", "rtl"] direction_mapping
|
112
|
-
# Direction mapping of term, `false` is used if there is an explicit direction mapping for this term
|
113
|
-
# @param [Boolean] reverse_property
|
114
|
-
# @param [Boolean] protected
|
115
|
-
# @param [String] nest term used for nest properties
|
116
|
-
# @param [Boolean] simple
|
117
|
-
# This is a simple term definition, not an expanded term definition
|
118
|
-
# @param [Boolean] prefix
|
119
|
-
# Term may be used as a prefix
|
120
|
-
def initialize(term,
|
121
|
-
id: nil,
|
122
|
-
index: nil,
|
123
|
-
type_mapping: nil,
|
124
|
-
container_mapping: nil,
|
125
|
-
language_mapping: nil,
|
126
|
-
direction_mapping: nil,
|
127
|
-
reverse_property: false,
|
128
|
-
nest: nil,
|
129
|
-
protected: nil,
|
130
|
-
simple: false,
|
131
|
-
prefix: nil,
|
132
|
-
context: nil)
|
133
|
-
@term = term
|
134
|
-
@id = id.to_s unless id.nil?
|
135
|
-
@index = index.to_s unless index.nil?
|
136
|
-
@type_mapping = type_mapping.to_s unless type_mapping.nil?
|
137
|
-
self.container_mapping = container_mapping
|
138
|
-
@language_mapping = language_mapping unless language_mapping.nil?
|
139
|
-
@direction_mapping = direction_mapping unless direction_mapping.nil?
|
140
|
-
@reverse_property = reverse_property
|
141
|
-
@protected = protected
|
142
|
-
@nest = nest unless nest.nil?
|
143
|
-
@simple = simple
|
144
|
-
@prefix = prefix unless prefix.nil?
|
145
|
-
@context = context unless context.nil?
|
146
|
-
end
|
147
|
-
|
148
|
-
# Term is protected.
|
149
|
-
# @return [Boolean]
|
150
|
-
def protected?; !!@protected; end
|
151
|
-
|
152
|
-
# Set container mapping, from an array which may include @set
|
153
|
-
def container_mapping=(mapping)
|
154
|
-
mapping = case mapping
|
155
|
-
when Set then mapping
|
156
|
-
when Array then Set.new(mapping)
|
157
|
-
when String then Set[mapping]
|
158
|
-
when nil then Set.new
|
159
|
-
else
|
160
|
-
raise "Shouldn't happen with #{mapping.inspect}"
|
161
|
-
end
|
162
|
-
if @as_set = mapping.include?('@set')
|
163
|
-
mapping = mapping.dup
|
164
|
-
mapping.delete('@set')
|
165
|
-
end
|
166
|
-
@container_mapping = mapping
|
167
|
-
@index ||= '@index' if mapping.include?('@index')
|
168
|
-
end
|
169
|
-
|
170
|
-
##
|
171
|
-
# Output Hash or String definition for this definition considering @language and @vocab
|
172
|
-
#
|
173
|
-
# @param [Context] context
|
174
|
-
# @return [String, Hash{String => Array[String], String}]
|
175
|
-
def to_context_definition(context)
|
176
|
-
cid = if context.vocab && id.start_with?(context.vocab)
|
177
|
-
# Nothing to return unless it's the same as the vocab
|
178
|
-
id == context.vocab ? context.vocab : id.to_s[context.vocab.length..-1]
|
179
|
-
else
|
180
|
-
# Find a term to act as a prefix
|
181
|
-
iri, prefix = context.iri_to_term.detect {|i,p| id.to_s.start_with?(i.to_s)}
|
182
|
-
iri && iri != id ? "#{prefix}:#{id.to_s[iri.length..-1]}" : id
|
183
|
-
end
|
184
|
-
|
185
|
-
if simple?
|
186
|
-
cid.to_s unless cid == term && context.vocab
|
187
|
-
else
|
188
|
-
defn = {}
|
189
|
-
defn[reverse_property ? '@reverse' : '@id'] = cid.to_s unless cid == term && !reverse_property
|
190
|
-
if type_mapping
|
191
|
-
defn['@type'] = if KEYWORDS.include?(type_mapping)
|
192
|
-
type_mapping
|
193
|
-
else
|
194
|
-
context.compact_iri(type_mapping, vocab: true)
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
cm = Array(container_mapping)
|
199
|
-
cm << "@set" if as_set? && !cm.include?("@set")
|
200
|
-
cm = cm.first if cm.length == 1
|
201
|
-
defn['@container'] = cm unless cm.empty?
|
202
|
-
# Language set as false to be output as null
|
203
|
-
defn['@language'] = (@language_mapping ? @language_mapping : nil) unless @language_mapping.nil?
|
204
|
-
defn['@context'] = @context if @context
|
205
|
-
defn['@nest'] = @nest if @nest
|
206
|
-
defn['@index'] = @index if @index
|
207
|
-
defn['@prefix'] = @prefix unless @prefix.nil?
|
208
|
-
defn
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
##
|
213
|
-
# Turn this into a source for a new instantiation
|
214
|
-
# FIXME: context serialization
|
215
|
-
# @return [String]
|
216
|
-
def to_rb
|
217
|
-
defn = [%(TermDefinition.new\(#{term.inspect})]
|
218
|
-
%w(id index type_mapping container_mapping language_mapping direction_mapping reverse_property nest simple prefix context protected).each do |acc|
|
219
|
-
v = instance_variable_get("@#{acc}".to_sym)
|
220
|
-
v = v.to_s if v.is_a?(RDF::Term)
|
221
|
-
if acc == 'container_mapping'
|
222
|
-
v = v.to_a
|
223
|
-
v << '@set' if as_set?
|
224
|
-
v = v.first if v.length <= 1
|
225
|
-
end
|
226
|
-
defn << "#{acc}: #{v.inspect}" if v
|
227
|
-
end
|
228
|
-
defn.join(', ') + ")"
|
229
|
-
end
|
230
|
-
|
231
|
-
# If container mapping was defined along with @set
|
232
|
-
# @return [Boolean]
|
233
|
-
def as_set?; @as_set || false; end
|
234
|
-
|
235
|
-
# Check if term definitions are identical, modulo @protected
|
236
|
-
# @return [Boolean]
|
237
|
-
def ==(other)
|
238
|
-
other.is_a?(TermDefinition) &&
|
239
|
-
id == other.id &&
|
240
|
-
term == other.term &&
|
241
|
-
type_mapping == other.type_mapping &&
|
242
|
-
container_mapping == other.container_mapping &&
|
243
|
-
nest == other.nest &&
|
244
|
-
language_mapping == other.language_mapping &&
|
245
|
-
direction_mapping == other.direction_mapping &&
|
246
|
-
reverse_property == other.reverse_property &&
|
247
|
-
simple == other.simple &&
|
248
|
-
index == other.index &&
|
249
|
-
context == other.context &&
|
250
|
-
prefix? == other.prefix? &&
|
251
|
-
as_set? == other.as_set?
|
252
|
-
end
|
253
|
-
|
254
|
-
def inspect
|
255
|
-
v = %w([TD)
|
256
|
-
v << "id=#{@id}"
|
257
|
-
v << "index=#{index.inspect}" unless index.nil?
|
258
|
-
v << "term=#{@term}"
|
259
|
-
v << "rev" if reverse_property
|
260
|
-
v << "container=#{container_mapping}" if container_mapping
|
261
|
-
v << "as_set=#{as_set?.inspect}"
|
262
|
-
v << "lang=#{language_mapping.inspect}" unless language_mapping.nil?
|
263
|
-
v << "dir=#{direction_mapping.inspect}" unless direction_mapping.nil?
|
264
|
-
v << "type=#{type_mapping}" unless type_mapping.nil?
|
265
|
-
v << "nest=#{nest.inspect}" unless nest.nil?
|
266
|
-
v << "simple=true" if @simple
|
267
|
-
v << "protected=true" if @protected
|
268
|
-
v << "prefix=#{@prefix.inspect}" unless @prefix.nil?
|
269
|
-
v << "has-context" unless context.nil?
|
270
|
-
v.join(" ") + "]"
|
271
|
-
end
|
272
|
-
end
|
273
|
-
|
274
53
|
# The base.
|
275
54
|
#
|
276
55
|
# @return [RDF::URI] Current base IRI, used for expanding relative IRIs.
|
277
56
|
attr_reader :base
|
278
57
|
|
279
|
-
# The base.
|
280
|
-
#
|
281
|
-
# @return [RDF::URI] Document base IRI, to initialize `base`.
|
282
|
-
attr_reader :doc_base
|
283
|
-
|
284
58
|
# @return [RDF::URI] base IRI of the context, if loaded remotely.
|
285
59
|
attr_accessor :context_base
|
286
60
|
|
@@ -321,9 +95,6 @@ module JSON::LD
|
|
321
95
|
# @return [Hash{Symbol => Object}] Global options used in generating IRIs
|
322
96
|
attr_accessor :options
|
323
97
|
|
324
|
-
# @return [Context] A context provided to us that we can use without re-serializing XXX
|
325
|
-
attr_accessor :provided_context
|
326
|
-
|
327
98
|
# @return [BlankNodeNamer]
|
328
99
|
attr_accessor :namer
|
329
100
|
|
@@ -333,11 +104,29 @@ module JSON::LD
|
|
333
104
|
# @see #initialize
|
334
105
|
# @see #parse
|
335
106
|
# @param [String, #read, Array, Hash, Context] local_context
|
107
|
+
# @param [String, #to_s] base (nil)
|
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.
|
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.
|
336
113
|
# @raise [JsonLdError]
|
337
114
|
# on a remote context load error, syntax error, or a reference to a term which is not defined.
|
338
115
|
# @return [Context]
|
339
|
-
def self.parse(local_context,
|
340
|
-
|
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
|
124
|
+
else
|
125
|
+
c.parse(local_context,
|
126
|
+
base: base,
|
127
|
+
override_protected: override_protected,
|
128
|
+
propagate: propagate)
|
129
|
+
end
|
341
130
|
end
|
342
131
|
|
343
132
|
##
|
@@ -346,17 +135,50 @@ module JSON::LD
|
|
346
135
|
# @return [RDF::Util::Cache]
|
347
136
|
# @private
|
348
137
|
def self.cache
|
349
|
-
require 'rdf/util/cache' unless defined?(::RDF::Util::Cache)
|
350
138
|
@cache ||= RDF::Util::Cache.new(CACHE_SIZE)
|
351
139
|
end
|
352
140
|
|
141
|
+
##
|
142
|
+
# Class-level cache inverse contexts.
|
143
|
+
#
|
144
|
+
# @return [RDF::Util::Cache]
|
145
|
+
# @private
|
146
|
+
def self.inverse_cache
|
147
|
+
@inverse_cache ||= RDF::Util::Cache.new(CACHE_SIZE)
|
148
|
+
end
|
149
|
+
|
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
|
170
|
+
end
|
171
|
+
else
|
172
|
+
# Don't try to cache
|
173
|
+
context = JSON::LD::Context.allocate
|
174
|
+
context.send(:initialize, **options)
|
175
|
+
context
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
353
179
|
##
|
354
180
|
# Create new evaluation context
|
355
181
|
# @param [Hash] options
|
356
|
-
# @option options [String, #to_s] :base
|
357
|
-
# 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.
|
358
|
-
# @option options [Proc] :documentLoader
|
359
|
-
# The callback of the loader to be used to retrieve remote documents and contexts. If specified, it must be used to retrieve remote documents and contexts; otherwise, if not specified, the processor's built-in loader must be used. See {API.documentLoader} for the method signature.
|
360
182
|
# @option options [Hash{Symbol => String}] :prefixes
|
361
183
|
# See `RDF::Reader#initialize`
|
362
184
|
# @option options [String, #to_s] :vocab
|
@@ -367,11 +189,9 @@ module JSON::LD
|
|
367
189
|
# @yieldparam [Context]
|
368
190
|
# @return [Context]
|
369
191
|
def initialize(**options)
|
370
|
-
if options[:
|
371
|
-
@
|
372
|
-
@doc_base.canonicalize! if options[:canonicalize]
|
192
|
+
if options[:processingMode] == 'json-ld-1.0'
|
193
|
+
@processingMode = 'json-ld-1.0'
|
373
194
|
end
|
374
|
-
self.processingMode = options[:processingMode] if options.has_key?(:processingMode)
|
375
195
|
@term_definitions = {}
|
376
196
|
@iri_to_term = {
|
377
197
|
RDF.to_uri.to_s => "rdf",
|
@@ -397,135 +217,6 @@ module JSON::LD
|
|
397
217
|
yield(self) if block_given?
|
398
218
|
end
|
399
219
|
|
400
|
-
##
|
401
|
-
# Initial context, without mappings, vocab or default language
|
402
|
-
#
|
403
|
-
# @return [Boolean]
|
404
|
-
def empty?
|
405
|
-
@term_definitions.empty? && self.vocab.nil? && self.default_language.nil?
|
406
|
-
end
|
407
|
-
|
408
|
-
# @param [String] value must be an absolute IRI
|
409
|
-
def base=(value, **options)
|
410
|
-
if value
|
411
|
-
raise JsonLdError::InvalidBaseIRI, "@base must be a string: #{value.inspect}" unless value.is_a?(String) || value.is_a?(RDF::URI)
|
412
|
-
value = RDF::URI(value).dup
|
413
|
-
value = @base.join(value) if @base && value.relative?
|
414
|
-
@base = value
|
415
|
-
@base.canonicalize! if @options[:canonicalize]
|
416
|
-
raise JsonLdError::InvalidBaseIRI, "@base must be an absolute IRI: #{value.inspect}" unless @base.absolute? || !@options[:validate]
|
417
|
-
@base
|
418
|
-
else
|
419
|
-
@base = nil
|
420
|
-
end
|
421
|
-
|
422
|
-
end
|
423
|
-
|
424
|
-
# @param [String] value
|
425
|
-
def default_language=(value, **options)
|
426
|
-
@default_language = case value
|
427
|
-
when String
|
428
|
-
# Warn on an invalid language tag, unless :validate is true, in which case it's an error
|
429
|
-
if value !~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/
|
430
|
-
warn "@language must be valid BCP47: #{value.inspect}"
|
431
|
-
end
|
432
|
-
options[:lowercaseLanguage] ? value.downcase : value
|
433
|
-
when nil
|
434
|
-
nil
|
435
|
-
else
|
436
|
-
raise JsonLdError::InvalidDefaultLanguage, "@language must be a string: #{value.inspect}"
|
437
|
-
end
|
438
|
-
end
|
439
|
-
|
440
|
-
# @param [String] value
|
441
|
-
def default_direction=(value, **options)
|
442
|
-
@default_direction = if value
|
443
|
-
raise JsonLdError::InvalidBaseDirection, "@direction must be one or 'ltr', or 'rtl': #{value.inspect}" unless %w(ltr rtl).include?(value)
|
444
|
-
value
|
445
|
-
else
|
446
|
-
nil
|
447
|
-
end
|
448
|
-
end
|
449
|
-
|
450
|
-
##
|
451
|
-
# Retrieve, or check processing mode.
|
452
|
-
#
|
453
|
-
# * With no arguments, retrieves the current set processingMode.
|
454
|
-
# * 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"
|
455
|
-
# * If expecting 1.1, and not set, it has the side-effect of setting mode to json-ld-1.1.
|
456
|
-
#
|
457
|
-
# @param [String, Number] expected (nil)
|
458
|
-
# @return [String]
|
459
|
-
def processingMode(expected = nil)
|
460
|
-
case expected
|
461
|
-
when 1.0, 'json-ld-1.0'
|
462
|
-
@processingMode == 'json-ld-1.0'
|
463
|
-
when 1.1, 'json-ld-1.1'
|
464
|
-
@processingMode ||= 'json-ld-1.1'
|
465
|
-
@processingMode == 'json-ld-1.1'
|
466
|
-
when nil
|
467
|
-
@processingMode
|
468
|
-
else
|
469
|
-
false
|
470
|
-
end
|
471
|
-
end
|
472
|
-
|
473
|
-
##
|
474
|
-
# Set processing mode.
|
475
|
-
#
|
476
|
-
# * 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"
|
477
|
-
#
|
478
|
-
# 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.
|
479
|
-
# If processingMode has been set, and it is not "json-ld-1.1", a "processing mode conflict" has been detecting, and processing is aborted.
|
480
|
-
#
|
481
|
-
# @param [String, Number] expected
|
482
|
-
# @return [String]
|
483
|
-
# @raise [JsonLdError::ProcessingModeConflict]
|
484
|
-
def processingMode=(value = nil, **options)
|
485
|
-
value = "json-ld-1.1" if value == 1.1
|
486
|
-
case value
|
487
|
-
when "json-ld-1.0", "json-ld-1.1"
|
488
|
-
if @processingMode && @processingMode != value
|
489
|
-
raise JsonLdError::ProcessingModeConflict, "#{value} not compatible with #{@processingMode}"
|
490
|
-
end
|
491
|
-
@processingMode = value
|
492
|
-
else
|
493
|
-
raise JsonLdError::InvalidVersionValue, value.inspect
|
494
|
-
end
|
495
|
-
end
|
496
|
-
|
497
|
-
# 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.
|
498
|
-
# @param [String] value must be an absolute IRI
|
499
|
-
def vocab=(value, **options)
|
500
|
-
@vocab = case value
|
501
|
-
when /_:/
|
502
|
-
# BNode vocab is deprecated
|
503
|
-
warn "[DEPRECATION] Blank Node vocabularies deprecated in JSON-LD 1.1." if @options[:validate] && processingMode("json-ld-1.1")
|
504
|
-
value
|
505
|
-
when String, RDF::URI
|
506
|
-
if (RDF::URI(value.to_s).relative? && processingMode("json-ld-1.0"))
|
507
|
-
raise JsonLdError::InvalidVocabMapping, "@vocab must be an absolute IRI in 1.0 mode: #{value.inspect}"
|
508
|
-
end
|
509
|
-
v = expand_iri(value.to_s, vocab: true, documentRelative: true)
|
510
|
-
raise JsonLdError::InvalidVocabMapping, "@vocab must be an IRI: #{value.inspect}" if !v.valid? && @options[:validate]
|
511
|
-
v
|
512
|
-
when nil
|
513
|
-
nil
|
514
|
-
else
|
515
|
-
raise JsonLdError::InvalidVocabMapping, "@vocab must be an IRI: #{value.inspect}"
|
516
|
-
end
|
517
|
-
end
|
518
|
-
|
519
|
-
# Set propagation
|
520
|
-
# @note: by the time this is called, the work has already been done.
|
521
|
-
#
|
522
|
-
# @param [Boolean] value
|
523
|
-
def propagate=(value, **options)
|
524
|
-
raise JsonLdError::InvalidContextEntry, "@propagate may only be set in 1.1 mode" if processingMode("json-ld-1.0")
|
525
|
-
raise JsonLdError::InvalidPropagateValue, "@propagate must be boolean valued: #{value.inspect}" unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
526
|
-
value
|
527
|
-
end
|
528
|
-
|
529
220
|
# Create an Evaluation Context
|
530
221
|
#
|
531
222
|
# When processing a JSON-LD data structure, each processing rule is applied using information provided by the active context. This section describes how to produce an active context.
|
@@ -536,10 +227,12 @@ module JSON::LD
|
|
536
227
|
#
|
537
228
|
#
|
538
229
|
# @param [String, #read, Array, Hash, Context] local_context
|
539
|
-
# @param [
|
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.
|
540
232
|
# @param [Boolean] override_protected Protected terms may be cleared.
|
541
|
-
# @param [Boolean] propagate
|
233
|
+
# @param [Boolean] propagate (true)
|
542
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 ([])
|
543
236
|
# @param [Boolean] validate_scoped (true).
|
544
237
|
# Validate scoped context, loading if necessary.
|
545
238
|
# If false, do not load scoped contexts.
|
@@ -548,12 +241,12 @@ module JSON::LD
|
|
548
241
|
# @return [Context]
|
549
242
|
# @see https://www.w3.org/TR/json-ld11-api/index.html#context-processing-algorithm
|
550
243
|
def parse(local_context,
|
551
|
-
|
244
|
+
base: nil,
|
552
245
|
override_protected: false,
|
553
246
|
propagate: true,
|
247
|
+
remote_contexts: [],
|
554
248
|
validate_scoped: true)
|
555
249
|
result = self.dup
|
556
|
-
result.provided_context = local_context if self.empty?
|
557
250
|
# Early check for @propagate, which can only appear in a local context
|
558
251
|
propagate = local_context.is_a?(Hash) ? local_context.fetch('@propagate', propagate) : propagate
|
559
252
|
result.previous_context ||= result.dup unless propagate
|
@@ -562,7 +255,7 @@ module JSON::LD
|
|
562
255
|
|
563
256
|
local_context.each do |context|
|
564
257
|
case context
|
565
|
-
when nil
|
258
|
+
when nil,false
|
566
259
|
# 3.1 If the `override_protected` is false, and the active context contains protected terms, an error is raised.
|
567
260
|
if override_protected || result.term_definitions.values.none?(&:protected?)
|
568
261
|
null_context = Context.new(**options)
|
@@ -581,18 +274,17 @@ module JSON::LD
|
|
581
274
|
begin
|
582
275
|
ctx = JSON.load(context)
|
583
276
|
raise JSON::LD::JsonLdError::InvalidRemoteContext, "Context missing @context key" if @options[:validate] && ctx['@context'].nil?
|
584
|
-
result = result.
|
585
|
-
result.provided_context = ctx["@context"] if [context] == local_context
|
277
|
+
result = result.parse(ctx["@context"] ? ctx["@context"] : {})
|
586
278
|
rescue JSON::ParserError => e
|
587
279
|
#log_debug("parse") {"Failed to parse @context from remote document at #{context}: #{e.message}"}
|
588
280
|
raise JSON::LD::JsonLdError::InvalidRemoteContext, "Failed to parse remote context at #{context}: #{e.message}" if @options[:validate]
|
589
|
-
self
|
281
|
+
self
|
590
282
|
end
|
591
283
|
when String, RDF::URI
|
592
284
|
#log_debug("parse") {"remote: #{context}, base: #{result.context_base || result.base}"}
|
593
285
|
|
594
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].
|
595
|
-
context = RDF::URI(result.context_base ||
|
287
|
+
context = RDF::URI(result.context_base || base).join(context)
|
596
288
|
context_canon = context.canonicalize
|
597
289
|
context_canon.scheme = 'http' if context_canon.scheme == 'https'
|
598
290
|
|
@@ -622,13 +314,13 @@ module JSON::LD
|
|
622
314
|
#context_opts.delete(:headers)
|
623
315
|
JSON::LD::API.loadRemoteDocument(context.to_s, **context_opts) do |remote_doc|
|
624
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.
|
625
|
-
raise JsonLdError::InvalidRemoteContext, "#{context}" unless remote_doc.document.is_a?(Hash) && remote_doc.document.
|
317
|
+
raise JsonLdError::InvalidRemoteContext, "#{context}" unless remote_doc.document.is_a?(Hash) && remote_doc.document.key?('@context')
|
626
318
|
|
627
319
|
# Parse stand-alone
|
628
|
-
ctx = Context.new(**options)
|
320
|
+
ctx = Context.new(unfrozen: true, **options).dup
|
629
321
|
ctx.context_base = context.to_s
|
630
322
|
ctx = ctx.parse(remote_doc.document['@context'], remote_contexts: remote_contexts.dup)
|
631
|
-
ctx.base
|
323
|
+
ctx.instance_variable_set(:@base, nil)
|
632
324
|
ctx
|
633
325
|
end
|
634
326
|
rescue JsonLdError::LoadingDocumentFailed => e
|
@@ -647,7 +339,6 @@ module JSON::LD
|
|
647
339
|
|
648
340
|
context.previous_context = self unless propagate
|
649
341
|
result = context
|
650
|
-
#log_debug("parse") {"=> provided_context: #{context.inspect}"}
|
651
342
|
when Hash
|
652
343
|
context = context.dup # keep from modifying a hash passed as a param
|
653
344
|
|
@@ -661,12 +352,12 @@ module JSON::LD
|
|
661
352
|
'@propagate' => :propagate=,
|
662
353
|
'@vocab' => :vocab=,
|
663
354
|
}.each do |key, setter|
|
664
|
-
next unless context.
|
355
|
+
next unless context.key?(key)
|
665
356
|
if key == '@import'
|
666
357
|
# Retrieve remote context and merge the remaining context object into the result.
|
667
358
|
raise JsonLdError::InvalidContextEntry, "@import may only be used in 1.1 mode}" if result.processingMode("json-ld-1.0")
|
668
359
|
raise JsonLdError::InvalidImportValue, "@import must be a string: #{context['@import'].inspect}" unless context['@import'].is_a?(String)
|
669
|
-
|
360
|
+
import_loc = RDF::URI(result.context_base || base).join(context['@import'])
|
670
361
|
begin
|
671
362
|
context_opts = @options.merge(
|
672
363
|
profile: 'http://www.w3.org/ns/json-ld#context',
|
@@ -674,22 +365,22 @@ module JSON::LD
|
|
674
365
|
base: nil)
|
675
366
|
context_opts.delete(:headers)
|
676
367
|
# FIXME: should cache this, but ContextCache is for parsed contexts
|
677
|
-
JSON::LD::API.loadRemoteDocument(
|
678
|
-
# Dereference
|
679
|
-
raise JsonLdError::InvalidRemoteContext, "#{
|
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.key?('@context')
|
680
371
|
import_context = remote_doc.document['@context']
|
681
372
|
import_context.delete('@base')
|
682
373
|
raise JsonLdError::InvalidRemoteContext, "#{import_context.to_json} must be an object" unless import_context.is_a?(Hash)
|
683
|
-
raise JsonLdError::InvalidContextEntry, "#{import_context.to_json} must not include @import entry" if import_context.
|
374
|
+
raise JsonLdError::InvalidContextEntry, "#{import_context.to_json} must not include @import entry" if import_context.key?('@import')
|
684
375
|
context.delete(key)
|
685
376
|
context = import_context.merge(context)
|
686
377
|
end
|
687
378
|
rescue JsonLdError::LoadingDocumentFailed => e
|
688
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{
|
379
|
+
raise JsonLdError::LoadingRemoteContextFailed, "#{import_loc}: #{e.message}", e.backtrace
|
689
380
|
rescue JsonLdError
|
690
381
|
raise
|
691
382
|
rescue StandardError => e
|
692
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{
|
383
|
+
raise JsonLdError::LoadingRemoteContextFailed, "#{import_loc}: #{e.message}", e.backtrace
|
693
384
|
end
|
694
385
|
else
|
695
386
|
result.send(setter, context[key], remote_contexts: remote_contexts)
|
@@ -703,6 +394,7 @@ module JSON::LD
|
|
703
394
|
context.each_key do |key|
|
704
395
|
# ... where key is not @base, @vocab, @language, or @version
|
705
396
|
result.create_term_definition(context, key, defined,
|
397
|
+
base: base,
|
706
398
|
override_protected: override_protected,
|
707
399
|
protected: context['@protected'],
|
708
400
|
remote_contexts: remote_contexts.dup,
|
@@ -721,18 +413,15 @@ module JSON::LD
|
|
721
413
|
# Merge in a context, creating a new context with updates from `context`
|
722
414
|
#
|
723
415
|
# @param [Context] context
|
724
|
-
# @param [Boolean] protected mark resulting context as protected
|
725
416
|
# @param [Boolean] override_protected Allow or disallow protected terms to be changed
|
726
|
-
# @param [Boolean]
|
727
417
|
# @return [Context]
|
728
418
|
def merge(context, override_protected: false)
|
729
|
-
ctx = Context.new(term_definitions: self.term_definitions
|
419
|
+
ctx = Context.new(term_definitions: self.term_definitions, standard_prefixes: options[:standard_prefixes])
|
730
420
|
ctx.context_base = context.context_base || self.context_base
|
731
421
|
ctx.default_language = context.default_language || self.default_language
|
732
422
|
ctx.default_direction = context.default_direction || self.default_direction
|
733
423
|
ctx.vocab = context.vocab || self.vocab
|
734
|
-
ctx.base =
|
735
|
-
ctx.provided_context = self.provided_context
|
424
|
+
ctx.base = self.base unless self.base.nil?
|
736
425
|
if !override_protected
|
737
426
|
ctx.term_definitions.each do |term, definition|
|
738
427
|
next unless definition.protected? && (other = context.term_definitions[term])
|
@@ -769,10 +458,9 @@ module JSON::LD
|
|
769
458
|
# @param [Hash] local_context
|
770
459
|
# @param [String] term
|
771
460
|
# @param [Hash] defined
|
461
|
+
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
772
462
|
# @param [Boolean] protected if true, causes all terms to be marked protected
|
773
463
|
# @param [Boolean] override_protected Protected terms may be cleared.
|
774
|
-
# @param [Boolean] propagate
|
775
|
-
# Context is propagated across node objects.
|
776
464
|
# @param [Array<String>] remote_contexts
|
777
465
|
# @param [Boolean] validate_scoped (true).
|
778
466
|
# Validate scoped context, loading if necessary.
|
@@ -781,6 +469,7 @@ module JSON::LD
|
|
781
469
|
# Represents a cyclical term dependency
|
782
470
|
# @see https://www.w3.org/TR/json-ld11-api/index.html#create-term-definition
|
783
471
|
def create_term_definition(local_context, term, defined,
|
472
|
+
base: nil,
|
784
473
|
override_protected: false,
|
785
474
|
protected: nil,
|
786
475
|
remote_contexts: [],
|
@@ -853,7 +542,7 @@ module JSON::LD
|
|
853
542
|
# Potentially note that the term is protected
|
854
543
|
definition.protected = value.fetch('@protected', protected)
|
855
544
|
|
856
|
-
if value.
|
545
|
+
if value.key?('@type')
|
857
546
|
type = value['@type']
|
858
547
|
# SPEC FIXME: @type may be nil
|
859
548
|
type = case type
|
@@ -877,7 +566,7 @@ module JSON::LD
|
|
877
566
|
definition.type_mapping = type
|
878
567
|
end
|
879
568
|
|
880
|
-
if value.
|
569
|
+
if value.key?('@reverse')
|
881
570
|
raise JsonLdError::InvalidReverseProperty, "unexpected key in #{value.inspect} on term #{term.inspect}" if
|
882
571
|
value.key?('@id') || value.key?('@nest')
|
883
572
|
raise JsonLdError::InvalidIRIMapping, "expected value of @reverse to be a string: #{value['@reverse'].inspect} on term #{term.inspect}" unless
|
@@ -903,7 +592,7 @@ module JSON::LD
|
|
903
592
|
warn "[DEPRECATION] Blank Node terms deprecated in JSON-LD 1.1." if @options[:validate] && processingMode('json-ld-1.1') && definition.id.to_s.start_with?("_:")
|
904
593
|
|
905
594
|
# If value contains an @container member, set the container mapping of definition to its value; if its value is neither @set, @index, @type, @id, an absolute IRI nor null, an invalid reverse property error has been detected (reverse properties only support set- and index-containers) and processing is aborted.
|
906
|
-
if value.
|
595
|
+
if value.key?('@container')
|
907
596
|
container = value['@container']
|
908
597
|
raise JsonLdError::InvalidReverseProperty,
|
909
598
|
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" unless
|
@@ -911,9 +600,9 @@ module JSON::LD
|
|
911
600
|
definition.container_mapping = check_container(container, local_context, defined, term)
|
912
601
|
end
|
913
602
|
definition.reverse_property = true
|
914
|
-
elsif value.
|
603
|
+
elsif value.key?('@id') && value['@id'].nil?
|
915
604
|
# Allowed to reserve a null term, which may be protected
|
916
|
-
elsif value.
|
605
|
+
elsif value.key?('@id') && value['@id'] != term
|
917
606
|
raise JsonLdError::InvalidIRIMapping, "expected value of @id to be a string: #{value['@id'].inspect} on term #{term.inspect}" unless
|
918
607
|
value['@id'].is_a?(String)
|
919
608
|
|
@@ -948,7 +637,7 @@ module JSON::LD
|
|
948
637
|
elsif term[1..-1].include?(':')
|
949
638
|
# If term is a compact IRI with a prefix that is a key in local context then a dependency has been found. Use this algorithm recursively passing active context, local context, the prefix as term, and defined.
|
950
639
|
prefix, suffix = term.split(':', 2)
|
951
|
-
create_term_definition(local_context, prefix, defined, protected: protected) if local_context.
|
640
|
+
create_term_definition(local_context, prefix, defined, protected: protected) if local_context.key?(prefix)
|
952
641
|
|
953
642
|
definition.id = if td = term_definitions[prefix]
|
954
643
|
# If term's prefix has a term definition in active context, set the IRI mapping for definition to the result of concatenating the value associated with the prefix's IRI mapping and the term's suffix.
|
@@ -975,7 +664,7 @@ module JSON::LD
|
|
975
664
|
|
976
665
|
@iri_to_term[definition.id] = term if simple_term && definition.id
|
977
666
|
|
978
|
-
if value.
|
667
|
+
if value.key?('@container')
|
979
668
|
#log_debug("") {"container_mapping: #{value['@container'].inspect}"}
|
980
669
|
definition.container_mapping = check_container(value['@container'], local_context, defined, term)
|
981
670
|
|
@@ -990,16 +679,17 @@ module JSON::LD
|
|
990
679
|
end
|
991
680
|
end
|
992
681
|
|
993
|
-
if value.
|
682
|
+
if value.key?('@index')
|
994
683
|
# property-based indexing
|
995
684
|
raise JsonLdError::InvalidTermDefinition, "@index without @index in @container: #{value['@index']} on term #{term.inspect}" unless definition.container_mapping.include?('@index')
|
996
685
|
raise JsonLdError::InvalidTermDefinition, "@index must expand to an IRI: #{value['@index']} on term #{term.inspect}" unless value['@index'].is_a?(String) && !value['@index'].start_with?('@')
|
997
686
|
definition.index = value['@index'].to_s
|
998
687
|
end
|
999
688
|
|
1000
|
-
if value.
|
689
|
+
if value.key?('@context')
|
1001
690
|
begin
|
1002
691
|
new_ctx = self.parse(value['@context'],
|
692
|
+
base: base,
|
1003
693
|
override_protected: true,
|
1004
694
|
remote_contexts: remote_contexts,
|
1005
695
|
validate_scoped: false)
|
@@ -1014,7 +704,7 @@ module JSON::LD
|
|
1014
704
|
end
|
1015
705
|
end
|
1016
706
|
|
1017
|
-
if value.
|
707
|
+
if value.key?('@language')
|
1018
708
|
language = value['@language']
|
1019
709
|
language = case value['@language']
|
1020
710
|
when String
|
@@ -1032,14 +722,14 @@ module JSON::LD
|
|
1032
722
|
definition.language_mapping = language || false
|
1033
723
|
end
|
1034
724
|
|
1035
|
-
if value.
|
725
|
+
if value.key?('@direction')
|
1036
726
|
direction = value['@direction']
|
1037
727
|
raise JsonLdError::InvalidBaseDirection, "direction must be null, 'ltr', or 'rtl', was #{language.inspect}} on term #{term.inspect}" unless direction.nil? || %w(ltr rtl).include?(direction)
|
1038
728
|
#log_debug("") {"direction_mapping: #{direction.inspect}"}
|
1039
729
|
definition.direction_mapping = direction || false
|
1040
730
|
end
|
1041
731
|
|
1042
|
-
if value.
|
732
|
+
if value.key?('@nest')
|
1043
733
|
nest = value['@nest']
|
1044
734
|
raise JsonLdError::InvalidNestValue, "nest must be a string, was #{nest.inspect}} on term #{term.inspect}" unless nest.is_a?(String)
|
1045
735
|
raise JsonLdError::InvalidNestValue, "nest must not be a keyword other than @nest, was #{nest.inspect}} on term #{term.inspect}" if nest.match?(/^@[a-zA-Z]+$/) && nest != '@nest'
|
@@ -1047,7 +737,7 @@ module JSON::LD
|
|
1047
737
|
definition.nest = nest
|
1048
738
|
end
|
1049
739
|
|
1050
|
-
if value.
|
740
|
+
if value.key?('@prefix')
|
1051
741
|
raise JsonLdError::InvalidTermDefinition, "@prefix used on compact or relative IRI term #{term.inspect}" if term.match?(%r{:|/})
|
1052
742
|
case pfx = value['@prefix']
|
1053
743
|
when TrueClass, FalseClass
|
@@ -1066,9 +756,130 @@ module JSON::LD
|
|
1066
756
|
|
1067
757
|
term_definitions[term] = definition
|
1068
758
|
defined[term] = true
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
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
|
1072
883
|
end
|
1073
884
|
|
1074
885
|
##
|
@@ -1077,40 +888,44 @@ module JSON::LD
|
|
1077
888
|
# If a context was supplied in global options, use that, otherwise, generate one
|
1078
889
|
# from this representation.
|
1079
890
|
#
|
891
|
+
# @param [Array, Hash, Context, IO, StringIO] provided_context (nil)
|
892
|
+
# Original context to use, if available
|
1080
893
|
# @param [Hash{Symbol => Object}] options ({})
|
1081
894
|
# @return [Hash]
|
1082
|
-
def serialize(**options)
|
1083
|
-
#
|
895
|
+
def serialize(provided_context: nil, **options)
|
896
|
+
#log_debug("serlialize: generate context")
|
897
|
+
#log_debug("") {"=> context: #{inspect}"}
|
1084
898
|
use_context = case provided_context
|
1085
899
|
when String, RDF::URI
|
1086
900
|
#log_debug "serlialize: reuse context: #{provided_context.inspect}"
|
1087
901
|
provided_context.to_s
|
1088
|
-
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
|
1089
907
|
#log_debug "serlialize: reuse context: #{provided_context.inspect}"
|
1090
908
|
provided_context
|
909
|
+
when IO, StringIO
|
910
|
+
provided_context.rewind
|
911
|
+
JSON.load(provided_context).fetch('@context', {})
|
1091
912
|
else
|
1092
|
-
#log_debug("serlialize: generate context")
|
1093
|
-
#log_debug("") {"=> context: #{inspect}"}
|
1094
913
|
ctx = {}
|
1095
|
-
ctx['@
|
914
|
+
ctx['@version'] = 1.1 if @processingMode == 'json-ld-1.1'
|
915
|
+
ctx['@base'] = base.to_s if base
|
1096
916
|
ctx['@direction'] = default_direction.to_s if default_direction
|
1097
917
|
ctx['@language'] = default_language.to_s if default_language
|
1098
918
|
ctx['@vocab'] = vocab.to_s if vocab
|
1099
919
|
|
1100
920
|
# Term Definitions
|
1101
|
-
term_definitions.
|
1102
|
-
|
1103
|
-
ctx[term] = defn if defn
|
921
|
+
term_definitions.each do |term, defn|
|
922
|
+
ctx[term] = defn.to_context_definition(self)
|
1104
923
|
end
|
1105
|
-
|
1106
|
-
#log_debug("") {"start_doc: context=#{ctx.inspect}"}
|
1107
924
|
ctx
|
1108
925
|
end
|
1109
926
|
|
1110
927
|
# Return hash with @context, or empty
|
1111
|
-
|
1112
|
-
r['@context'] = use_context unless use_context.nil? || use_context.empty?
|
1113
|
-
r
|
928
|
+
use_context.nil? || use_context.empty? ? {} : {'@context' => use_context}
|
1114
929
|
end
|
1115
930
|
|
1116
931
|
##
|
@@ -1203,7 +1018,7 @@ module JSON::LD
|
|
1203
1018
|
|
1204
1019
|
term_sym = term.empty? ? "" : term.to_sym
|
1205
1020
|
iri_to_term.delete(term_definitions[term].id.to_s) if term_definitions[term].id.is_a?(String)
|
1206
|
-
@options[:prefixes][term_sym] = value if @options.
|
1021
|
+
@options[:prefixes][term_sym] = value if @options.key?(:prefixes)
|
1207
1022
|
iri_to_term[value.to_s] = term
|
1208
1023
|
term_definitions[term]
|
1209
1024
|
end
|
@@ -1223,7 +1038,7 @@ module JSON::LD
|
|
1223
1038
|
# @param [Term, #to_s] term in unexpanded form
|
1224
1039
|
# @return [Array<'@index', '@language', '@index', '@set', '@type', '@id', '@graph'>]
|
1225
1040
|
def container(term)
|
1226
|
-
return [term] if term == '@list'
|
1041
|
+
return Set[term] if term == '@list'
|
1227
1042
|
term = find_definition(term)
|
1228
1043
|
term ? term.container_mapping : Set.new
|
1229
1044
|
end
|
@@ -1319,7 +1134,7 @@ module JSON::LD
|
|
1319
1134
|
# @return [Term] related term definition
|
1320
1135
|
def reverse_term(term)
|
1321
1136
|
# Direct lookup of term
|
1322
|
-
term = term_definitions[term.to_s] if term_definitions.
|
1137
|
+
term = term_definitions[term.to_s] if term_definitions.key?(term.to_s) && !term.is_a?(TermDefinition)
|
1323
1138
|
|
1324
1139
|
# Lookup term, assuming term is an IRI
|
1325
1140
|
unless term.is_a?(TermDefinition)
|
@@ -1338,26 +1153,27 @@ module JSON::LD
|
|
1338
1153
|
#
|
1339
1154
|
# @param [String] value
|
1340
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.
|
1341
1160
|
# @param [Boolean] documentRelative (false)
|
1342
|
-
# @param [Boolean] vocab (false)
|
1343
1161
|
# @param [Hash] local_context
|
1344
1162
|
# Used during Context Processing.
|
1345
|
-
# @param [
|
1346
|
-
# Used during Context Processing.
|
1347
|
-
# @param [Boolean] as_string (false) transform RDF::Resource values to string
|
1163
|
+
# @param [Boolean] vocab (false)
|
1348
1164
|
# @param [Hash{Symbol => Object}] options
|
1349
1165
|
# @return [RDF::Resource, String]
|
1350
1166
|
# IRI or String, if it's a keyword
|
1351
1167
|
# @raise [JSON::LD::JsonLdError::InvalidIRIMapping] if the value cannot be expanded
|
1352
1168
|
# @see https://www.w3.org/TR/json-ld11-api/#iri-expansion
|
1353
1169
|
def expand_iri(value,
|
1170
|
+
as_string: false,
|
1171
|
+
base: nil,
|
1172
|
+
defined: nil,
|
1354
1173
|
documentRelative: false,
|
1355
|
-
vocab: false,
|
1356
1174
|
local_context: nil,
|
1357
|
-
|
1358
|
-
|
1359
|
-
**options
|
1360
|
-
)
|
1175
|
+
vocab: false,
|
1176
|
+
**options)
|
1361
1177
|
return (value && as_string ? value.to_s : value) unless value.is_a?(String)
|
1362
1178
|
|
1363
1179
|
return value if KEYWORDS.include?(value)
|
@@ -1366,7 +1182,7 @@ module JSON::LD
|
|
1366
1182
|
defined = defined || {} # if we initialized in the keyword arg we would allocate {} at each invokation, even in the 2 (common) early returns above.
|
1367
1183
|
|
1368
1184
|
# If local context is not null, it contains a key that equals value, and the value associated with the key that equals value in defined is not true, then invoke the Create Term Definition subalgorithm, passing active context, local context, value as term, and defined. This will ensure that a term definition is created for value in active context during Context Processing.
|
1369
|
-
if local_context && local_context.
|
1185
|
+
if local_context && local_context.key?(value) && !defined[value]
|
1370
1186
|
create_term_definition(local_context, value, defined)
|
1371
1187
|
end
|
1372
1188
|
|
@@ -1377,7 +1193,8 @@ module JSON::LD
|
|
1377
1193
|
# If active context has a term definition for value, and the associated mapping is a keyword, return that keyword.
|
1378
1194
|
# If vocab is true and the active context has a term definition for value, return the associated IRI mapping.
|
1379
1195
|
if (v_td = term_definitions[value]) && (vocab || KEYWORDS.include?(v_td.id))
|
1380
|
-
|
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)
|
1381
1198
|
end
|
1382
1199
|
|
1383
1200
|
# If value contains a colon (:), it is either an absolute IRI or a compact IRI:
|
@@ -1395,7 +1212,7 @@ module JSON::LD
|
|
1395
1212
|
end
|
1396
1213
|
|
1397
1214
|
# If local context is not null, it contains a key that equals prefix, and the value associated with the key that equals prefix in defined is not true, invoke the Create Term Definition algorithm, passing active context, local context, prefix as term, and defined. This will ensure that a term definition is created for prefix in active context during Context Processing.
|
1398
|
-
if local_context && local_context.
|
1215
|
+
if local_context && local_context.key?(prefix) && !defined[prefix]
|
1399
1216
|
create_term_definition(local_context, prefix, defined)
|
1400
1217
|
end
|
1401
1218
|
|
@@ -1410,18 +1227,32 @@ module JSON::LD
|
|
1410
1227
|
end
|
1411
1228
|
end
|
1412
1229
|
|
1230
|
+
iri = value.is_a?(RDF::URI) ? value : RDF::URI(value)
|
1413
1231
|
result = if vocab && self.vocab
|
1414
1232
|
# If vocab is true, and active context has a vocabulary mapping, return the result of concatenating the vocabulary mapping with value.
|
1415
|
-
|
1416
|
-
|
1417
|
-
|
1418
|
-
|
1419
|
-
|
1420
|
-
|
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?
|
1421
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.
|
1422
1253
|
raise JSON::LD::JsonLdError::InvalidIRIMapping, "not an absolute IRI: #{value}"
|
1423
1254
|
else
|
1424
|
-
|
1255
|
+
iri
|
1425
1256
|
end
|
1426
1257
|
result && as_string ? result.to_s : result
|
1427
1258
|
end
|
@@ -1442,21 +1273,21 @@ module JSON::LD
|
|
1442
1273
|
# Compacts an absolute IRI to the shortest matching term or compact IRI
|
1443
1274
|
#
|
1444
1275
|
# @param [RDF::URI] iri
|
1276
|
+
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1445
1277
|
# @param [Object] value
|
1446
1278
|
# Value, used to select among various maps for the same IRI
|
1447
|
-
# @param [Boolean] vocab
|
1448
|
-
# specifies whether the passed iri should be compacted using the active context's vocabulary mapping
|
1449
1279
|
# @param [Boolean] reverse
|
1450
1280
|
# specifies whether a reverse property is being compacted
|
1451
|
-
# @param
|
1281
|
+
# @param [Boolean] vocab
|
1282
|
+
# specifies whether the passed iri should be compacted using the active context's vocabulary mapping
|
1452
1283
|
#
|
1453
1284
|
# @return [String] compacted form of IRI
|
1454
1285
|
# @see https://www.w3.org/TR/json-ld11-api/#iri-compaction
|
1455
|
-
def compact_iri(iri,
|
1286
|
+
def compact_iri(iri, base: nil, reverse: false, value: nil, vocab: nil)
|
1456
1287
|
return if iri.nil?
|
1457
1288
|
iri = iri.to_s
|
1458
1289
|
|
1459
|
-
if vocab && inverse_context.
|
1290
|
+
if vocab && inverse_context.key?(iri)
|
1460
1291
|
default_language = if self.default_direction
|
1461
1292
|
"#{self.default_language}_#{self.default_direction}".downcase
|
1462
1293
|
else
|
@@ -1467,7 +1298,7 @@ module JSON::LD
|
|
1467
1298
|
containers.concat(CONTAINERS_INDEX_SET) if index?(value) && !graph?(value)
|
1468
1299
|
|
1469
1300
|
# If the value is a JSON Object with the key @preserve, use the value of @preserve.
|
1470
|
-
value = value['@preserve'].first if value.is_a?(Hash) && value.
|
1301
|
+
value = value['@preserve'].first if value.is_a?(Hash) && value.key?('@preserve')
|
1471
1302
|
|
1472
1303
|
if reverse
|
1473
1304
|
tl, tl_value = "@type", "@reverse"
|
@@ -1481,11 +1312,11 @@ module JSON::LD
|
|
1481
1312
|
list.each do |item|
|
1482
1313
|
item_language, item_type = "@none", "@none"
|
1483
1314
|
if value?(item)
|
1484
|
-
if item.
|
1315
|
+
if item.key?('@direction')
|
1485
1316
|
item_language = "#{item['@language']}_#{item['@direction']}".downcase
|
1486
|
-
elsif item.
|
1317
|
+
elsif item.key?('@language')
|
1487
1318
|
item_language = item['@language'].downcase
|
1488
|
-
elsif item.
|
1319
|
+
elsif item.key?('@type')
|
1489
1320
|
item_type = item['@type']
|
1490
1321
|
else
|
1491
1322
|
item_language = "@null"
|
@@ -1513,14 +1344,14 @@ module JSON::LD
|
|
1513
1344
|
elsif graph?(value)
|
1514
1345
|
# Prefer @index and @id containers, then @graph, then @index
|
1515
1346
|
containers.concat(CONTAINERS_GRAPH_INDEX_INDEX) if index?(value)
|
1516
|
-
containers.concat(CONTAINERS_GRAPH) if value.
|
1347
|
+
containers.concat(CONTAINERS_GRAPH) if value.key?('@id')
|
1517
1348
|
|
1518
1349
|
# Prefer an @graph container next
|
1519
1350
|
containers.concat(CONTAINERS_GRAPH_SET)
|
1520
1351
|
|
1521
1352
|
# Lastly, in 1.1, any graph can be indexed on @index or @id, so add if we haven't already
|
1522
1353
|
containers.concat(CONTAINERS_GRAPH_INDEX) unless index?(value)
|
1523
|
-
containers.concat(CONTAINERS_GRAPH) unless value.
|
1354
|
+
containers.concat(CONTAINERS_GRAPH) unless value.key?('@id')
|
1524
1355
|
containers.concat(CONTAINERS_INDEX_SET) unless index?(value)
|
1525
1356
|
containers << '@set'
|
1526
1357
|
|
@@ -1528,13 +1359,13 @@ module JSON::LD
|
|
1528
1359
|
else
|
1529
1360
|
if value?(value)
|
1530
1361
|
# In 1.1, an language map can be used to index values using @none
|
1531
|
-
if value.
|
1362
|
+
if value.key?('@language') && !index?(value)
|
1532
1363
|
tl_value = value['@language'].downcase
|
1533
1364
|
tl_value += "_#{value['@direction']}" if value['@direction']
|
1534
1365
|
containers.concat(CONTAINERS_LANGUAGE)
|
1535
|
-
elsif value.
|
1366
|
+
elsif value.key?('@direction') && !index?(value)
|
1536
1367
|
tl_value = "_#{value['@direction']}"
|
1537
|
-
elsif value.
|
1368
|
+
elsif value.key?('@type')
|
1538
1369
|
tl_value = value['@type']
|
1539
1370
|
tl = '@type'
|
1540
1371
|
end
|
@@ -1556,8 +1387,8 @@ module JSON::LD
|
|
1556
1387
|
tl_value ||= '@null'
|
1557
1388
|
preferred_values = []
|
1558
1389
|
preferred_values << '@reverse' if tl_value == '@reverse'
|
1559
|
-
if (tl_value == '@id' || tl_value == '@reverse') && value.is_a?(Hash) && value.
|
1560
|
-
t_iri = compact_iri(value['@id'], vocab: true,
|
1390
|
+
if (tl_value == '@id' || tl_value == '@reverse') && value.is_a?(Hash) && value.key?('@id')
|
1391
|
+
t_iri = compact_iri(value['@id'], vocab: true, base: base)
|
1561
1392
|
if (r_td = term_definitions[t_iri]) && r_td.id == value['@id']
|
1562
1393
|
preferred_values.concat(CONTAINERS_VOCAB_ID)
|
1563
1394
|
else
|
@@ -1582,7 +1413,7 @@ module JSON::LD
|
|
1582
1413
|
# At this point, there is no simple term that iri can be compacted to. If vocab is true and active context has a vocabulary mapping:
|
1583
1414
|
if vocab && self.vocab && iri.start_with?(self.vocab) && iri.length > self.vocab.length
|
1584
1415
|
suffix = iri[self.vocab.length..-1]
|
1585
|
-
return suffix unless term_definitions.
|
1416
|
+
return suffix unless term_definitions.key?(suffix)
|
1586
1417
|
end
|
1587
1418
|
|
1588
1419
|
# The iri could not be compacted using the active context's vocabulary mapping. Try to create a compact IRI, starting by initializing compact IRI to null. This variable will be used to tore the created compact IRI, if any.
|
@@ -1596,7 +1427,7 @@ module JSON::LD
|
|
1596
1427
|
|
1597
1428
|
suffix = iri[td.id.length..-1]
|
1598
1429
|
ciri = "#{term}:#{suffix}"
|
1599
|
-
candidates << ciri unless value && term_definitions.
|
1430
|
+
candidates << ciri unless value && term_definitions.key?(ciri)
|
1600
1431
|
end
|
1601
1432
|
|
1602
1433
|
return candidates.sort.first if !candidates.empty?
|
@@ -1623,7 +1454,7 @@ module JSON::LD
|
|
1623
1454
|
|
1624
1455
|
if !vocab
|
1625
1456
|
# transform iri to a relative IRI using the document's base IRI
|
1626
|
-
iri = remove_base(iri)
|
1457
|
+
iri = remove_base(self.base || base, iri)
|
1627
1458
|
return iri
|
1628
1459
|
else
|
1629
1460
|
return iri
|
@@ -1643,26 +1474,24 @@ module JSON::LD
|
|
1643
1474
|
# Value (literal or IRI) to be expanded
|
1644
1475
|
# @param [Boolean] useNativeTypes (false) use native representations
|
1645
1476
|
# @param [Boolean] rdfDirection (nil) decode i18n datatype if i18n-datatype
|
1477
|
+
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1646
1478
|
# @param [Hash{Symbol => Object}] options
|
1647
1479
|
#
|
1648
1480
|
# @return [Hash] Object representation of value
|
1649
1481
|
# @raise [RDF::ReaderError] if the iri cannot be expanded
|
1650
1482
|
# @see https://www.w3.org/TR/json-ld11-api/#value-expansion
|
1651
|
-
def expand_value(property, value, useNativeTypes: false, rdfDirection: nil, **options)
|
1652
|
-
#log_debug("expand_value") {"property: #{property.inspect}, value: #{value.inspect}"}
|
1653
|
-
|
1483
|
+
def expand_value(property, value, useNativeTypes: false, rdfDirection: nil, base: nil, **options)
|
1654
1484
|
td = term_definitions.fetch(property, TermDefinition.new(property))
|
1655
1485
|
|
1656
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.
|
1657
1487
|
if value.is_a?(String) && td.type_mapping == '@id'
|
1658
1488
|
#log_debug("") {"as relative IRI: #{value.inspect}"}
|
1659
|
-
return {'@id' => expand_iri(value, documentRelative: true).to_s}
|
1489
|
+
return {'@id' => expand_iri(value, documentRelative: true, base: base).to_s}
|
1660
1490
|
end
|
1661
1491
|
|
1662
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.
|
1663
1493
|
if value.is_a?(String) && td.type_mapping == '@vocab'
|
1664
|
-
|
1665
|
-
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}
|
1666
1495
|
end
|
1667
1496
|
|
1668
1497
|
value = RDF::Literal(value) if
|
@@ -1672,16 +1501,14 @@ module JSON::LD
|
|
1672
1501
|
|
1673
1502
|
result = case value
|
1674
1503
|
when RDF::URI, RDF::Node
|
1675
|
-
#log_debug("URI | BNode") { value.to_s }
|
1676
1504
|
{'@id' => value.to_s}
|
1677
1505
|
when RDF::Literal
|
1678
|
-
#log_debug("Literal") {"datatype: #{value.datatype.inspect}"}
|
1679
1506
|
res = {}
|
1680
1507
|
if value.datatype == RDF::URI(RDF.to_uri + "JSON") && processingMode('json-ld-1.1')
|
1681
1508
|
# Value parsed as JSON
|
1682
1509
|
# FIXME: MultiJson
|
1683
|
-
res['@value'] = ::JSON.parse(value.object)
|
1684
1510
|
res['@type'] = '@json'
|
1511
|
+
res['@value'] = ::JSON.parse(value.object)
|
1685
1512
|
elsif value.datatype.start_with?("https://www.w3.org/ns/i18n#") && rdfDirection == 'i18n-datatype' && processingMode('json-ld-1.1')
|
1686
1513
|
lang, dir = value.datatype.fragment.split('_')
|
1687
1514
|
res['@value'] = value.to_s
|
@@ -1696,25 +1523,24 @@ module JSON::LD
|
|
1696
1523
|
res['@language'] = lang
|
1697
1524
|
end
|
1698
1525
|
res['@direction'] = dir
|
1699
|
-
elsif useNativeTypes && RDF_LITERAL_NATIVE_TYPES.include?(value.datatype)
|
1700
|
-
res['@value'] = value.object
|
1526
|
+
elsif useNativeTypes && RDF_LITERAL_NATIVE_TYPES.include?(value.datatype) && value.valid?
|
1701
1527
|
res['@type'] = uri(coerce(property)) if coerce(property)
|
1528
|
+
res['@value'] = value.object
|
1702
1529
|
else
|
1703
|
-
value.canonicalize! if value.datatype == RDF::XSD.double
|
1704
|
-
res['@value'] = value.to_s
|
1530
|
+
value.canonicalize! if value.valid? && value.datatype == RDF::XSD.double
|
1705
1531
|
if coerce(property)
|
1706
1532
|
res['@type'] = uri(coerce(property)).to_s
|
1707
|
-
elsif value.
|
1533
|
+
elsif value.datatype?
|
1708
1534
|
res['@type'] = uri(value.datatype).to_s
|
1709
|
-
elsif value.
|
1535
|
+
elsif value.language? || language(property)
|
1710
1536
|
res['@language'] = (value.language || language(property)).to_s
|
1711
|
-
# FIXME: direction
|
1712
1537
|
end
|
1538
|
+
res['@value'] = value.to_s
|
1713
1539
|
end
|
1714
1540
|
res
|
1715
1541
|
else
|
1716
1542
|
# Otherwise, initialize result to a JSON object with an @value member whose value is set to value.
|
1717
|
-
res = {
|
1543
|
+
res = {}
|
1718
1544
|
|
1719
1545
|
if td.type_mapping && !CONTAINERS_ID_VOCAB.include?(td.type_mapping.to_s)
|
1720
1546
|
res['@type'] = td.type_mapping.to_s
|
@@ -1725,10 +1551,9 @@ module JSON::LD
|
|
1725
1551
|
res['@direction'] = direction if direction
|
1726
1552
|
end
|
1727
1553
|
|
1728
|
-
res
|
1554
|
+
res.merge('@value' => value)
|
1729
1555
|
end
|
1730
1556
|
|
1731
|
-
#log_debug("") {"=> #{result.inspect}"}
|
1732
1557
|
result
|
1733
1558
|
rescue ::JSON::ParserError => e
|
1734
1559
|
raise JSON::LD::JsonLdError::InvalidJsonLiteral, e.message
|
@@ -1741,13 +1566,13 @@ module JSON::LD
|
|
1741
1566
|
# Associated property used to find coercion rules
|
1742
1567
|
# @param [Hash] value
|
1743
1568
|
# Value (literal or IRI), in full object representation, to be compacted
|
1744
|
-
# @param
|
1569
|
+
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1745
1570
|
#
|
1746
1571
|
# @return [Hash] Object representation of value
|
1747
1572
|
# @raise [JsonLdError] if the iri cannot be expanded
|
1748
1573
|
# @see https://www.w3.org/TR/json-ld11-api/#value-compaction
|
1749
1574
|
# FIXME: revisit the specification version of this.
|
1750
|
-
def compact_value(property, value,
|
1575
|
+
def compact_value(property, value, base: nil)
|
1751
1576
|
#log_debug("compact_value") {"property: #{property.inspect}, value: #{value.inspect}"}
|
1752
1577
|
|
1753
1578
|
indexing = index?(value) && container(property).include?('@index')
|
@@ -1755,15 +1580,15 @@ module JSON::LD
|
|
1755
1580
|
direction = direction(property)
|
1756
1581
|
|
1757
1582
|
result = case
|
1758
|
-
when coerce(property) == '@id' && value.
|
1583
|
+
when coerce(property) == '@id' && value.key?('@id') && (value.keys - %w(@id @index)).empty?
|
1759
1584
|
# Compact an @id coercion
|
1760
1585
|
#log_debug("") {" (@id & coerce)"}
|
1761
|
-
compact_iri(value['@id'])
|
1762
|
-
when coerce(property) == '@vocab' && value.
|
1586
|
+
compact_iri(value['@id'], base: base)
|
1587
|
+
when coerce(property) == '@vocab' && value.key?('@id') && (value.keys - %w(@id @index)).empty?
|
1763
1588
|
# Compact an @id coercion
|
1764
1589
|
#log_debug("") {" (@id & coerce & vocab)"}
|
1765
1590
|
compact_iri(value['@id'], vocab: true)
|
1766
|
-
when value.
|
1591
|
+
when value.key?('@id')
|
1767
1592
|
#log_debug("") {" (@id)"}
|
1768
1593
|
# return value as is
|
1769
1594
|
value
|
@@ -1784,7 +1609,7 @@ module JSON::LD
|
|
1784
1609
|
value
|
1785
1610
|
end
|
1786
1611
|
|
1787
|
-
if result.is_a?(Hash) && result.
|
1612
|
+
if result.is_a?(Hash) && result.key?('@type') && value['@type'] != '@json'
|
1788
1613
|
# Compact values of @type
|
1789
1614
|
c_type = if result['@type'].is_a?(Array)
|
1790
1615
|
result['@type'].map {|t| compact_iri(t, vocab: true)}
|
@@ -1848,16 +1673,21 @@ module JSON::LD
|
|
1848
1673
|
v.join(" ") + "]"
|
1849
1674
|
end
|
1850
1675
|
|
1676
|
+
# Duplicate an active context, allowing it to be modified.
|
1851
1677
|
def dup
|
1852
|
-
# Also duplicate mappings, coerce and list
|
1853
1678
|
that = self
|
1854
|
-
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
|
+
|
1855
1688
|
ec.instance_eval do
|
1856
|
-
@term_definitions = that.term_definitions.
|
1857
|
-
|
1858
|
-
end
|
1859
|
-
@iri_to_term = that.iri_to_term.dup
|
1860
|
-
@inverse_context = nil
|
1689
|
+
@term_definitions = that.term_definitions.dup
|
1690
|
+
@iri_to_term = that.iri_to_term
|
1861
1691
|
end
|
1862
1692
|
ec
|
1863
1693
|
end
|
@@ -1903,20 +1733,11 @@ module JSON::LD
|
|
1903
1733
|
bnode(namer.get_sym($1))
|
1904
1734
|
else
|
1905
1735
|
value = RDF::URI(value)
|
1906
|
-
value.validate! if
|
1907
|
-
value.canonicalize! if @options[:canonicalize]
|
1908
|
-
value = RDF::URI.intern(value, {}) if @options[:intern]
|
1736
|
+
#value.validate! if options[:validate]
|
1909
1737
|
value
|
1910
1738
|
end
|
1911
1739
|
end
|
1912
1740
|
|
1913
|
-
# Clear the provided context, used for testing
|
1914
|
-
# @return [Context] self
|
1915
|
-
def clear_provided_context
|
1916
|
-
@provided_context = nil
|
1917
|
-
self
|
1918
|
-
end
|
1919
|
-
|
1920
1741
|
# Keep track of allocated BNodes
|
1921
1742
|
#
|
1922
1743
|
# Don't actually use the name provided, to prevent name alias issues.
|
@@ -1957,7 +1778,7 @@ module JSON::LD
|
|
1957
1778
|
# @return [Hash{String => Hash{String => String}}]
|
1958
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
|
1959
1780
|
def inverse_context
|
1960
|
-
|
1781
|
+
Context.inverse_cache[self.hash] ||= begin
|
1961
1782
|
result = {}
|
1962
1783
|
default_language = (self.default_language || '@none').downcase
|
1963
1784
|
term_definitions.keys.sort do |a, b|
|
@@ -2036,11 +1857,11 @@ module JSON::LD
|
|
2036
1857
|
container_map = inverse_context[iri]
|
2037
1858
|
#log_debug(" ") {"container_map: #{container_map.inspect}"}
|
2038
1859
|
containers.each do |container|
|
2039
|
-
next unless container_map.
|
1860
|
+
next unless container_map.key?(container)
|
2040
1861
|
tl_map = container_map[container]
|
2041
1862
|
value_map = tl_map[type_language]
|
2042
1863
|
preferred_values.each do |item|
|
2043
|
-
next unless value_map.
|
1864
|
+
next unless value_map.key?(item)
|
2044
1865
|
#log_debug("=>") {value_map[item].inspect}
|
2045
1866
|
return value_map[item]
|
2046
1867
|
end
|
@@ -2052,10 +1873,11 @@ module JSON::LD
|
|
2052
1873
|
##
|
2053
1874
|
# Removes a base IRI from the given absolute IRI.
|
2054
1875
|
#
|
1876
|
+
# @param [String] base the base used for making `iri` relative
|
2055
1877
|
# @param [String] iri the absolute IRI
|
2056
1878
|
# @return [String]
|
2057
1879
|
# the relative IRI if relative to base, otherwise the absolute IRI.
|
2058
|
-
def remove_base(iri)
|
1880
|
+
def remove_base(base, iri)
|
2059
1881
|
return iri unless base
|
2060
1882
|
@base_and_parents ||= begin
|
2061
1883
|
u = base
|
@@ -2165,5 +1987,232 @@ module JSON::LD
|
|
2165
1987
|
end
|
2166
1988
|
Array(container)
|
2167
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
|
2168
2217
|
end
|
2169
2218
|
end
|