json-ld 3.2.3 → 3.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/json/ld/api.rb +807 -764
- data/lib/json/ld/compact.rb +304 -304
- data/lib/json/ld/conneg.rb +179 -161
- data/lib/json/ld/context.rb +2080 -1945
- data/lib/json/ld/expand.rb +745 -666
- data/lib/json/ld/extensions.rb +14 -13
- data/lib/json/ld/flatten.rb +257 -247
- data/lib/json/ld/format.rb +202 -194
- data/lib/json/ld/frame.rb +525 -502
- data/lib/json/ld/from_rdf.rb +224 -166
- data/lib/json/ld/html/nokogiri.rb +123 -121
- data/lib/json/ld/html/rexml.rb +151 -147
- data/lib/json/ld/reader.rb +107 -100
- data/lib/json/ld/resource.rb +224 -205
- data/lib/json/ld/streaming_reader.rb +574 -507
- data/lib/json/ld/streaming_writer.rb +93 -92
- data/lib/json/ld/to_rdf.rb +171 -167
- data/lib/json/ld/utils.rb +270 -264
- data/lib/json/ld/version.rb +24 -14
- data/lib/json/ld/writer.rb +334 -311
- data/lib/json/ld.rb +103 -96
- metadata +78 -209
- data/spec/api_spec.rb +0 -132
- data/spec/compact_spec.rb +0 -3482
- data/spec/conneg_spec.rb +0 -373
- data/spec/context_spec.rb +0 -2036
- data/spec/expand_spec.rb +0 -4496
- data/spec/flatten_spec.rb +0 -1203
- data/spec/format_spec.rb +0 -115
- data/spec/frame_spec.rb +0 -2498
- data/spec/from_rdf_spec.rb +0 -1005
- data/spec/matchers.rb +0 -20
- data/spec/rdfstar_spec.rb +0 -25
- data/spec/reader_spec.rb +0 -883
- data/spec/resource_spec.rb +0 -76
- data/spec/spec_helper.rb +0 -281
- data/spec/streaming_reader_spec.rb +0 -237
- data/spec/streaming_writer_spec.rb +0 -145
- data/spec/suite_compact_spec.rb +0 -22
- data/spec/suite_expand_spec.rb +0 -36
- data/spec/suite_flatten_spec.rb +0 -34
- data/spec/suite_frame_spec.rb +0 -29
- data/spec/suite_from_rdf_spec.rb +0 -22
- data/spec/suite_helper.rb +0 -411
- data/spec/suite_html_spec.rb +0 -22
- data/spec/suite_http_spec.rb +0 -35
- data/spec/suite_remote_doc_spec.rb +0 -22
- data/spec/suite_to_rdf_spec.rb +0 -30
- data/spec/support/extensions.rb +0 -44
- data/spec/test-files/test-1-compacted.jsonld +0 -10
- data/spec/test-files/test-1-context.jsonld +0 -7
- data/spec/test-files/test-1-expanded.jsonld +0 -5
- data/spec/test-files/test-1-input.jsonld +0 -10
- data/spec/test-files/test-1-rdf.ttl +0 -8
- data/spec/test-files/test-2-compacted.jsonld +0 -20
- data/spec/test-files/test-2-context.jsonld +0 -7
- data/spec/test-files/test-2-expanded.jsonld +0 -16
- data/spec/test-files/test-2-input.jsonld +0 -20
- data/spec/test-files/test-2-rdf.ttl +0 -14
- data/spec/test-files/test-3-compacted.jsonld +0 -11
- data/spec/test-files/test-3-context.jsonld +0 -8
- data/spec/test-files/test-3-expanded.jsonld +0 -10
- data/spec/test-files/test-3-input.jsonld +0 -11
- data/spec/test-files/test-3-rdf.ttl +0 -8
- data/spec/test-files/test-4-compacted.jsonld +0 -10
- data/spec/test-files/test-4-context.jsonld +0 -7
- data/spec/test-files/test-4-expanded.jsonld +0 -6
- data/spec/test-files/test-4-input.jsonld +0 -10
- data/spec/test-files/test-4-rdf.ttl +0 -5
- data/spec/test-files/test-5-compacted.jsonld +0 -13
- data/spec/test-files/test-5-context.jsonld +0 -7
- data/spec/test-files/test-5-expanded.jsonld +0 -9
- data/spec/test-files/test-5-input.jsonld +0 -13
- data/spec/test-files/test-5-rdf.ttl +0 -7
- data/spec/test-files/test-6-compacted.jsonld +0 -10
- data/spec/test-files/test-6-context.jsonld +0 -7
- data/spec/test-files/test-6-expanded.jsonld +0 -10
- data/spec/test-files/test-6-input.jsonld +0 -10
- data/spec/test-files/test-6-rdf.ttl +0 -6
- data/spec/test-files/test-7-compacted.jsonld +0 -23
- data/spec/test-files/test-7-context.jsonld +0 -4
- data/spec/test-files/test-7-expanded.jsonld +0 -20
- data/spec/test-files/test-7-input.jsonld +0 -23
- data/spec/test-files/test-7-rdf.ttl +0 -14
- data/spec/test-files/test-8-compacted.jsonld +0 -34
- data/spec/test-files/test-8-context.jsonld +0 -11
- data/spec/test-files/test-8-expanded.jsonld +0 -24
- data/spec/test-files/test-8-frame.jsonld +0 -18
- data/spec/test-files/test-8-framed.jsonld +0 -25
- data/spec/test-files/test-8-input.jsonld +0 -30
- data/spec/test-files/test-8-rdf.ttl +0 -15
- data/spec/test-files/test-9-compacted.jsonld +0 -20
- data/spec/test-files/test-9-context.jsonld +0 -13
- data/spec/test-files/test-9-expanded.jsonld +0 -14
- data/spec/test-files/test-9-input.jsonld +0 -12
- data/spec/to_rdf_spec.rb +0 -1551
- data/spec/writer_spec.rb +0 -427
data/lib/json/ld/context.rb
CHANGED
@@ -1,2223 +1,2358 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
1
|
# frozen_string_literal: true
|
2
|
+
|
3
3
|
require 'json'
|
4
4
|
require 'bigdecimal'
|
5
5
|
require 'set'
|
6
6
|
require 'rdf/util/cache'
|
7
7
|
|
8
|
-
module JSON
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
##
|
14
|
-
# Preloaded contexts.
|
15
|
-
# To avoid runtime context parsing and downloading, contexts may be pre-loaded by implementations.
|
16
|
-
# @return [Hash{Symbol => Context}]
|
17
|
-
PRELOADED = {}
|
8
|
+
module JSON
|
9
|
+
module LD
|
10
|
+
class Context
|
11
|
+
include Utils
|
12
|
+
include RDF::Util::Logger
|
18
13
|
|
19
|
-
|
20
|
-
|
14
|
+
##
|
15
|
+
# Preloaded contexts.
|
16
|
+
# To avoid runtime context parsing and downloading, contexts may be pre-loaded by implementations.
|
17
|
+
# @return [Hash{Symbol => Context}]
|
18
|
+
PRELOADED = {}
|
21
19
|
|
22
|
-
|
23
|
-
|
24
|
-
# cached in memory at any one time.
|
25
|
-
CACHE_SIZE = 100 # unlimited by default
|
20
|
+
# Initial contexts, defined on first access
|
21
|
+
INITIAL_CONTEXTS = {}
|
26
22
|
|
27
|
-
class << self
|
28
23
|
##
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
|
33
|
-
|
34
|
-
|
24
|
+
# Defines the maximum number of interned URI references that can be held
|
25
|
+
# cached in memory at any one time.
|
26
|
+
CACHE_SIZE = 100 # unlimited by default
|
27
|
+
|
28
|
+
class << self
|
29
|
+
##
|
30
|
+
# Add preloaded context. In the block form, the context is lazy evaulated on first use.
|
31
|
+
# @param [String, RDF::URI] url
|
32
|
+
# @param [Context] context (nil)
|
33
|
+
# @yieldreturn [Context]
|
34
|
+
def add_preloaded(url, context = nil, &block)
|
35
|
+
PRELOADED[url.to_s.freeze] = context || block
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Alias a previousliy loaded context
|
40
|
+
# @param [String, RDF::URI] a
|
41
|
+
# @param [String, RDF::URI] url
|
42
|
+
def alias_preloaded(a, url)
|
43
|
+
PRELOADED[a.to_s.freeze] = PRELOADED[url.to_s.freeze]
|
44
|
+
end
|
35
45
|
end
|
36
46
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
PRELOADED[a.to_s.freeze] = PRELOADED[url.to_s.freeze]
|
47
|
+
begin
|
48
|
+
# Attempt to load this to avoid unnecessary context fetches
|
49
|
+
require 'json/ld/preloaded'
|
50
|
+
rescue LoadError
|
51
|
+
# Silently allow this to fail
|
43
52
|
end
|
44
|
-
end
|
45
53
|
|
46
|
-
|
47
|
-
#
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
54
|
+
# The base.
|
55
|
+
#
|
56
|
+
# @return [RDF::URI] Current base IRI, used for expanding relative IRIs.
|
57
|
+
attr_reader :base
|
58
|
+
|
59
|
+
# @return [RDF::URI] base IRI of the context, if loaded remotely.
|
60
|
+
attr_accessor :context_base
|
61
|
+
|
62
|
+
# Term definitions
|
63
|
+
# @return [Hash{String => TermDefinition}]
|
64
|
+
attr_reader :term_definitions
|
65
|
+
|
66
|
+
# @return [Hash{RDF::URI => String}] Reverse mappings from IRI to term only for terms, not CURIEs XXX
|
67
|
+
attr_accessor :iri_to_term
|
68
|
+
|
69
|
+
# Previous definition for this context. This is used for rolling back type-scoped contexts.
|
70
|
+
# @return [Context]
|
71
|
+
attr_accessor :previous_context
|
72
|
+
|
73
|
+
# Context is property-scoped
|
74
|
+
# @return [Boolean]
|
75
|
+
attr_accessor :property_scoped
|
76
|
+
|
77
|
+
# Default language
|
78
|
+
#
|
79
|
+
# This adds a language to plain strings that aren't otherwise coerced
|
80
|
+
# @return [String]
|
81
|
+
attr_reader :default_language
|
52
82
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
##
|
102
|
-
# Create a new context by parsing a context.
|
103
|
-
#
|
104
|
-
# @see #initialize
|
105
|
-
# @see #parse
|
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.
|
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
|
124
|
-
else
|
125
|
-
c.parse(local_context,
|
126
|
-
base: base,
|
127
|
-
override_protected: override_protected,
|
128
|
-
propagate: propagate)
|
83
|
+
# Default direction
|
84
|
+
#
|
85
|
+
# This adds a direction to plain strings that aren't otherwise coerced
|
86
|
+
# @return ["lrt", "rtl"]
|
87
|
+
attr_reader :default_direction
|
88
|
+
|
89
|
+
# Default vocabulary
|
90
|
+
#
|
91
|
+
# Sets the default vocabulary used for expanding terms which
|
92
|
+
# aren't otherwise absolute IRIs
|
93
|
+
# @return [RDF::URI]
|
94
|
+
attr_reader :vocab
|
95
|
+
|
96
|
+
# @return [Hash{Symbol => Object}] Global options used in generating IRIs
|
97
|
+
attr_accessor :options
|
98
|
+
|
99
|
+
# @return [BlankNodeNamer]
|
100
|
+
attr_accessor :namer
|
101
|
+
|
102
|
+
##
|
103
|
+
# Create a new context by parsing a context.
|
104
|
+
#
|
105
|
+
# @see #initialize
|
106
|
+
# @see #parse
|
107
|
+
# @param [String, #read, Array, Hash, Context] local_context
|
108
|
+
# @param [String, #to_s] base (nil)
|
109
|
+
# 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.
|
110
|
+
# @param [Boolean] override_protected (false)
|
111
|
+
# Protected terms may be cleared.
|
112
|
+
# @param [Boolean] propagate (true)
|
113
|
+
# If false, retains any previously defined term, which can be rolled back when the descending into a new node object changes.
|
114
|
+
# @raise [JsonLdError]
|
115
|
+
# on a remote context load error, syntax error, or a reference to a term which is not defined.
|
116
|
+
# @return [Context]
|
117
|
+
def self.parse(local_context,
|
118
|
+
base: nil,
|
119
|
+
override_protected: false,
|
120
|
+
propagate: true,
|
121
|
+
**options)
|
122
|
+
c = new(**options)
|
123
|
+
if local_context.respond_to?(:empty?) && local_context.empty?
|
124
|
+
c
|
125
|
+
else
|
126
|
+
c.parse(local_context,
|
127
|
+
base: base,
|
128
|
+
override_protected: override_protected,
|
129
|
+
propagate: propagate)
|
130
|
+
end
|
129
131
|
end
|
130
|
-
end
|
131
132
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
133
|
+
##
|
134
|
+
# Class-level cache used for retaining parsed remote contexts.
|
135
|
+
#
|
136
|
+
# @return [RDF::Util::Cache]
|
137
|
+
# @private
|
138
|
+
def self.cache
|
139
|
+
@cache ||= RDF::Util::Cache.new(CACHE_SIZE)
|
140
|
+
end
|
140
141
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
142
|
+
##
|
143
|
+
# Class-level cache inverse contexts.
|
144
|
+
#
|
145
|
+
# @return [RDF::Util::Cache]
|
146
|
+
# @private
|
147
|
+
def self.inverse_cache
|
148
|
+
@inverse_cache ||= RDF::Util::Cache.new(CACHE_SIZE)
|
149
|
+
end
|
149
150
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
151
|
+
##
|
152
|
+
# @private
|
153
|
+
# Allow caching of well-known contexts
|
154
|
+
def self.new(**options)
|
155
|
+
if (options.keys - %i[
|
156
|
+
compactArrays
|
157
|
+
documentLoader
|
158
|
+
extractAllScripts
|
159
|
+
ordered
|
160
|
+
processingMode
|
161
|
+
validate
|
162
|
+
]).empty?
|
163
|
+
# allow caching
|
164
|
+
key = options.hash
|
165
|
+
INITIAL_CONTEXTS[key] ||= begin
|
166
|
+
context = JSON::LD::Context.allocate
|
167
|
+
context.send(:initialize, **options)
|
168
|
+
context.freeze
|
169
|
+
context.term_definitions.freeze
|
170
|
+
context
|
171
|
+
end
|
172
|
+
else
|
173
|
+
# Don't try to cache
|
165
174
|
context = JSON::LD::Context.allocate
|
166
175
|
context.send(:initialize, **options)
|
167
|
-
context.freeze
|
168
|
-
context.term_definitions.freeze
|
169
176
|
context
|
170
177
|
end
|
171
|
-
else
|
172
|
-
# Don't try to cache
|
173
|
-
context = JSON::LD::Context.allocate
|
174
|
-
context.send(:initialize, **options)
|
175
|
-
context
|
176
178
|
end
|
177
|
-
end
|
178
179
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
@
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
end
|
180
|
+
##
|
181
|
+
# Create new evaluation context
|
182
|
+
# @param [Hash] options
|
183
|
+
# @option options [Hash{Symbol => String}] :prefixes
|
184
|
+
# See `RDF::Reader#initialize`
|
185
|
+
# @option options [String, #to_s] :vocab
|
186
|
+
# Initial value for @vocab
|
187
|
+
# @option options [String, #to_s] :language
|
188
|
+
# Initial value for @langauge
|
189
|
+
# @yield [ec]
|
190
|
+
# @yieldparam [Context]
|
191
|
+
# @return [Context]
|
192
|
+
def initialize(**options)
|
193
|
+
@processingMode = 'json-ld-1.0' if options[:processingMode] == 'json-ld-1.0'
|
194
|
+
@term_definitions = {}
|
195
|
+
@iri_to_term = {
|
196
|
+
RDF.to_uri.to_s => "rdf",
|
197
|
+
RDF::XSD.to_uri.to_s => "xsd"
|
198
|
+
}
|
199
|
+
@namer = BlankNodeMapper.new("t")
|
200
|
+
|
201
|
+
@options = options
|
202
|
+
|
203
|
+
# Load any defined prefixes
|
204
|
+
(options[:prefixes] || {}).each_pair do |k, v|
|
205
|
+
next if k.nil?
|
206
|
+
|
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
210
|
|
211
|
-
|
212
|
-
|
213
|
-
|
211
|
+
self.vocab = options[:vocab] if options[:vocab]
|
212
|
+
self.default_language = options[:language] if /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/.match?(options[:language])
|
213
|
+
@term_definitions = options[:term_definitions] if options[:term_definitions]
|
214
214
|
|
215
|
-
|
215
|
+
# log_debug("init") {"iri_to_term: #{iri_to_term.inspect}"}
|
216
216
|
|
217
|
-
|
218
|
-
|
217
|
+
yield(self) if block_given?
|
218
|
+
end
|
219
219
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
220
|
+
# Create an Evaluation Context
|
221
|
+
#
|
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.
|
223
|
+
#
|
224
|
+
# The active context contains the active term definitions which specify how properties and values have to be interpreted as well as the current base IRI, the vocabulary mapping and the default language. Each term definition consists of an IRI mapping, a boolean flag reverse property, an optional type mapping or language mapping, and an optional container mapping. A term definition can not only be used to map a term to an IRI, but also to map a term to a keyword, in which case it is referred to as a keyword alias.
|
225
|
+
#
|
226
|
+
# When processing, the active context is initialized without any term definitions, vocabulary mapping, or default language. If a local context is encountered during processing, a new active context is created by cloning the existing active context. Then the information from the local context is merged into the new active context. Given that local contexts may contain references to remote contexts, this includes their retrieval.
|
227
|
+
#
|
228
|
+
#
|
229
|
+
# @param [String, #read, Array, Hash, Context] local_context
|
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.
|
232
|
+
# @param [Boolean] override_protected Protected terms may be cleared.
|
233
|
+
# @param [Boolean] propagate (true)
|
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.
|
239
|
+
# @raise [JsonLdError]
|
240
|
+
# on a remote context load error, syntax error, or a reference to a term which is not defined.
|
241
|
+
# @return [Context]
|
242
|
+
# @see https://www.w3.org/TR/json-ld11-api/index.html#context-processing-algorithm
|
243
|
+
def parse(local_context,
|
244
|
+
base: nil,
|
245
|
+
override_protected: false,
|
246
|
+
propagate: true,
|
247
|
+
remote_contexts: [],
|
248
|
+
validate_scoped: true)
|
249
|
+
result = dup
|
250
|
+
# Early check for @propagate, which can only appear in a local context
|
251
|
+
propagate = local_context.is_a?(Hash) ? local_context.fetch('@propagate', propagate) : propagate
|
252
|
+
result.previous_context ||= result.dup unless propagate
|
253
|
+
|
254
|
+
local_context = as_array(local_context)
|
255
|
+
|
256
|
+
log_depth do
|
257
|
+
local_context.each do |context|
|
258
|
+
case context
|
259
|
+
when nil, false
|
260
|
+
# 3.1 If the `override_protected` is false, and the active context contains protected terms, an error is raised.
|
261
|
+
if override_protected || result.term_definitions.values.none?(&:protected?)
|
262
|
+
null_context = Context.new(**options)
|
263
|
+
null_context.previous_context = result unless propagate
|
264
|
+
result = null_context
|
265
|
+
else
|
266
|
+
raise JSON::LD::JsonLdError::InvalidContextNullification,
|
267
267
|
"Attempt to clear a context with protected terms"
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
raise JSON::LD::JsonLdError::InvalidRemoteContext, "Failed to parse remote context at #{context}: #{e.message}" if @options[:validate]
|
282
|
-
self
|
283
|
-
end
|
284
|
-
when String, RDF::URI
|
285
|
-
log_debug("parse") {"remote: #{context}, base: #{result.context_base || result.base}"}
|
268
|
+
end
|
269
|
+
when Context
|
270
|
+
# log_debug("parse") {"context: #{context.inspect}"}
|
271
|
+
result = result.merge(context)
|
272
|
+
when IO, StringIO
|
273
|
+
# log_debug("parse") {"io: #{context}"}
|
274
|
+
# Load context document, if it is an open file
|
275
|
+
begin
|
276
|
+
ctx = load_context(context, **@options)
|
277
|
+
if @options[:validate] && ctx['@context'].nil?
|
278
|
+
raise JSON::LD::JsonLdError::InvalidRemoteContext,
|
279
|
+
"Context missing @context key"
|
280
|
+
end
|
286
281
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
282
|
+
result = result.parse(ctx["@context"] || {})
|
283
|
+
rescue JSON::ParserError => e
|
284
|
+
log_info("parse") { "Failed to parse @context from remote document at #{context}: #{e.message}" }
|
285
|
+
if @options[:validate]
|
286
|
+
raise JSON::LD::JsonLdError::InvalidRemoteContext,
|
287
|
+
"Failed to parse remote context at #{context}: #{e.message}"
|
288
|
+
end
|
291
289
|
|
292
|
-
|
293
|
-
|
290
|
+
self
|
291
|
+
end
|
292
|
+
when String, RDF::URI
|
293
|
+
# log_debug("parse") {"remote: #{context}, base: #{result.context_base || result.base}"}
|
294
294
|
|
295
|
-
|
296
|
-
|
295
|
+
# 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].
|
296
|
+
context = RDF::URI(result.context_base || base).join(context)
|
297
|
+
context_canon = context.canonicalize
|
298
|
+
context_canon.scheme = 'http' if context_canon.scheme == 'https'
|
297
299
|
|
298
|
-
|
299
|
-
|
300
|
-
log_debug("parse") {"=> cached_context: #{context_canon.to_s.inspect}"}
|
300
|
+
# If validating a scoped context which has already been loaded, skip to the next one
|
301
|
+
next if !validate_scoped && remote_contexts.include?(context.to_s)
|
301
302
|
|
302
|
-
|
303
|
-
|
304
|
-
log_debug("parse") {"=> (call)"}
|
305
|
-
PRELOADED[context_canon.to_s] = PRELOADED[context_canon.to_s].call
|
306
|
-
end
|
307
|
-
PRELOADED[context_canon.to_s]
|
308
|
-
else
|
309
|
-
# Load context document, if it is a string
|
310
|
-
Context.cache[context_canon.to_s] ||= begin
|
311
|
-
context_opts = @options.merge(
|
312
|
-
profile: 'http://www.w3.org/ns/json-ld#context',
|
313
|
-
requestProfile: 'http://www.w3.org/ns/json-ld#context',
|
314
|
-
base: nil)
|
315
|
-
#context_opts.delete(:headers)
|
316
|
-
JSON::LD::API.loadRemoteDocument(context.to_s, **context_opts) do |remote_doc|
|
317
|
-
# 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.
|
318
|
-
raise JsonLdError::InvalidRemoteContext, "#{context}" unless remote_doc.document.is_a?(Hash) && remote_doc.document.key?('@context')
|
319
|
-
|
320
|
-
# Parse stand-alone
|
321
|
-
ctx = Context.new(unfrozen: true, **options).dup
|
322
|
-
ctx.context_base = context.to_s
|
323
|
-
ctx = ctx.parse(remote_doc.document['@context'], remote_contexts: remote_contexts.dup)
|
324
|
-
ctx.context_base = context.to_s # In case it was altered
|
325
|
-
ctx.instance_variable_set(:@base, nil)
|
326
|
-
ctx
|
327
|
-
end
|
328
|
-
rescue JsonLdError::LoadingDocumentFailed => e
|
329
|
-
log_info("parse") {"Failed to retrieve @context from remote document at #{context_canon.inspect}: #{e.message}"}
|
330
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{context}: #{e.message}", e.backtrace
|
331
|
-
rescue JsonLdError
|
332
|
-
raise
|
333
|
-
rescue StandardError => e
|
334
|
-
log_info("parse") {"Failed to retrieve @context from remote document at #{context_canon.inspect}: #{e.message}"}
|
335
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{context}: #{e.message}", e.backtrace
|
336
|
-
end
|
337
|
-
end
|
303
|
+
remote_contexts << context.to_s
|
304
|
+
raise JsonLdError::ContextOverflow, context.to_s if remote_contexts.length >= MAX_CONTEXTS_LOADED
|
338
305
|
|
339
|
-
|
340
|
-
|
306
|
+
cached_context = if PRELOADED[context_canon.to_s]
|
307
|
+
# If we have a cached context, merge it into the current context (result) and use as the new context
|
308
|
+
# log_debug("parse") {"=> cached_context: #{context_canon.to_s.inspect}"}
|
341
309
|
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
context.
|
378
|
-
|
310
|
+
# If this is a Proc, then replace the entry with the result of running the Proc
|
311
|
+
if PRELOADED[context_canon.to_s].respond_to?(:call)
|
312
|
+
# log_debug("parse") {"=> (call)"}
|
313
|
+
PRELOADED[context_canon.to_s] = PRELOADED[context_canon.to_s].call
|
314
|
+
end
|
315
|
+
PRELOADED[context_canon.to_s].context_base ||= context_canon.to_s
|
316
|
+
PRELOADED[context_canon.to_s]
|
317
|
+
else
|
318
|
+
# Load context document, if it is a string
|
319
|
+
Context.cache[context_canon.to_s] ||= begin
|
320
|
+
context_opts = @options.merge(
|
321
|
+
profile: 'http://www.w3.org/ns/json-ld#context',
|
322
|
+
requestProfile: 'http://www.w3.org/ns/json-ld#context',
|
323
|
+
base: nil
|
324
|
+
)
|
325
|
+
# context_opts.delete(:headers)
|
326
|
+
JSON::LD::API.loadRemoteDocument(context.to_s, **context_opts) do |remote_doc|
|
327
|
+
# 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.
|
328
|
+
unless remote_doc.document.is_a?(Hash) && remote_doc.document.key?('@context')
|
329
|
+
raise JsonLdError::InvalidRemoteContext,
|
330
|
+
context.to_s
|
331
|
+
end
|
332
|
+
|
333
|
+
# Parse stand-alone
|
334
|
+
ctx = Context.new(unfrozen: true, **options).dup
|
335
|
+
ctx.context_base = context.to_s
|
336
|
+
ctx = ctx.parse(remote_doc.document['@context'], remote_contexts: remote_contexts.dup)
|
337
|
+
ctx.context_base = context.to_s # In case it was altered
|
338
|
+
ctx.instance_variable_set(:@base, nil)
|
339
|
+
ctx
|
340
|
+
end
|
341
|
+
rescue JsonLdError::LoadingDocumentFailed => e
|
342
|
+
log_info("parse") do
|
343
|
+
"Failed to retrieve @context from remote document at #{context_canon.inspect}: #{e.message}"
|
344
|
+
end
|
345
|
+
raise JsonLdError::LoadingRemoteContextFailed, "#{context}: #{e.message}", e.backtrace
|
346
|
+
rescue JsonLdError
|
347
|
+
raise
|
348
|
+
rescue StandardError => e
|
349
|
+
log_info("parse") do
|
350
|
+
"Failed to retrieve @context from remote document at #{context_canon.inspect}: #{e.message}"
|
351
|
+
end
|
352
|
+
raise JsonLdError::LoadingRemoteContextFailed, "#{context}: #{e.message}", e.backtrace
|
379
353
|
end
|
380
|
-
rescue JsonLdError::LoadingDocumentFailed => e
|
381
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{import_loc}: #{e.message}", e.backtrace
|
382
|
-
rescue JsonLdError
|
383
|
-
raise
|
384
|
-
rescue StandardError => e
|
385
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{import_loc}: #{e.message}", e.backtrace
|
386
354
|
end
|
387
|
-
else
|
388
|
-
result.send(setter, context[key], remote_contexts: remote_contexts)
|
389
|
-
end
|
390
|
-
context.delete(key)
|
391
|
-
end
|
392
355
|
|
393
|
-
|
356
|
+
# Merge loaded context noting protected term overriding
|
357
|
+
context = result.merge(cached_context, override_protected: override_protected)
|
358
|
+
|
359
|
+
context.previous_context = self unless propagate
|
360
|
+
result = context
|
361
|
+
when Hash
|
362
|
+
context = context.dup # keep from modifying a hash passed as a param
|
363
|
+
|
364
|
+
# This counts on hash elements being processed in order
|
365
|
+
{
|
366
|
+
'@version' => :processingMode=,
|
367
|
+
'@import' => nil,
|
368
|
+
'@base' => :base=,
|
369
|
+
'@direction' => :default_direction=,
|
370
|
+
'@language' => :default_language=,
|
371
|
+
'@propagate' => :propagate=,
|
372
|
+
'@vocab' => :vocab=
|
373
|
+
}.each do |key, setter|
|
374
|
+
next unless context.key?(key)
|
375
|
+
|
376
|
+
if key == '@import'
|
377
|
+
# Retrieve remote context and merge the remaining context object into the result.
|
378
|
+
if result.processingMode("json-ld-1.0")
|
379
|
+
raise JsonLdError::InvalidContextEntry,
|
380
|
+
"@import may only be used in 1.1 mode}"
|
381
|
+
end
|
382
|
+
unless context['@import'].is_a?(String)
|
383
|
+
raise JsonLdError::InvalidImportValue,
|
384
|
+
"@import must be a string: #{context['@import'].inspect}"
|
385
|
+
end
|
386
|
+
|
387
|
+
import_loc = RDF::URI(result.context_base || base).join(context['@import'])
|
388
|
+
begin
|
389
|
+
context_opts = @options.merge(
|
390
|
+
profile: 'http://www.w3.org/ns/json-ld#context',
|
391
|
+
requestProfile: 'http://www.w3.org/ns/json-ld#context',
|
392
|
+
base: nil
|
393
|
+
)
|
394
|
+
context_opts.delete(:headers)
|
395
|
+
# FIXME: should cache this, but ContextCache is for parsed contexts
|
396
|
+
JSON::LD::API.loadRemoteDocument(import_loc, **context_opts) do |remote_doc|
|
397
|
+
# 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.
|
398
|
+
unless remote_doc.document.is_a?(Hash) && remote_doc.document.key?('@context')
|
399
|
+
raise JsonLdError::InvalidRemoteContext,
|
400
|
+
import_loc.to_s
|
401
|
+
end
|
402
|
+
|
403
|
+
import_context = remote_doc.document['@context']
|
404
|
+
import_context.delete('@base')
|
405
|
+
unless import_context.is_a?(Hash)
|
406
|
+
raise JsonLdError::InvalidRemoteContext,
|
407
|
+
"#{import_context.to_json} must be an object"
|
408
|
+
end
|
409
|
+
if import_context.key?('@import')
|
410
|
+
raise JsonLdError::InvalidContextEntry,
|
411
|
+
"#{import_context.to_json} must not include @import entry"
|
412
|
+
end
|
413
|
+
|
414
|
+
context.delete(key)
|
415
|
+
context = import_context.merge(context)
|
416
|
+
end
|
417
|
+
rescue JsonLdError::LoadingDocumentFailed => e
|
418
|
+
raise JsonLdError::LoadingRemoteContextFailed, "#{import_loc}: #{e.message}", e.backtrace
|
419
|
+
rescue JsonLdError
|
420
|
+
raise
|
421
|
+
rescue StandardError => e
|
422
|
+
raise JsonLdError::LoadingRemoteContextFailed, "#{import_loc}: #{e.message}", e.backtrace
|
423
|
+
end
|
424
|
+
else
|
425
|
+
result.send(setter, context[key], remote_contexts: remote_contexts)
|
426
|
+
end
|
427
|
+
context.delete(key)
|
428
|
+
end
|
429
|
+
|
430
|
+
defined = {}
|
394
431
|
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
432
|
+
# For each key-value pair in context invoke the Create Term Definition subalgorithm, passing result for active context, context for local context, key, and defined
|
433
|
+
context.each_key do |key|
|
434
|
+
# ... where key is not @base, @vocab, @language, or @version
|
435
|
+
next if NON_TERMDEF_KEYS.include?(key)
|
436
|
+
|
437
|
+
result.create_term_definition(context, key, defined,
|
438
|
+
base: base,
|
439
|
+
override_protected: override_protected,
|
440
|
+
protected: context['@protected'],
|
441
|
+
remote_contexts: remote_contexts.dup,
|
442
|
+
validate_scoped: validate_scoped)
|
443
|
+
end
|
444
|
+
else
|
445
|
+
# 3.3) If context is not a JSON object, an invalid local context error has been detected and processing is aborted.
|
446
|
+
raise JsonLdError::InvalidLocalContext, "must be a URL, JSON object or array of same: #{context.inspect}"
|
447
|
+
end
|
405
448
|
end
|
406
|
-
else
|
407
|
-
# 3.3) If context is not a JSON object, an invalid local context error has been detected and processing is aborted.
|
408
|
-
raise JsonLdError::InvalidLocalContext, "must be a URL, JSON object or array of same: #{context.inspect}"
|
409
449
|
end
|
450
|
+
result
|
410
451
|
end
|
411
|
-
end
|
412
|
-
result
|
413
|
-
end
|
414
452
|
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
453
|
+
##
|
454
|
+
# Merge in a context, creating a new context with updates from `context`
|
455
|
+
#
|
456
|
+
# @param [Context] context
|
457
|
+
# @param [Boolean] override_protected Allow or disallow protected terms to be changed
|
458
|
+
# @return [Context]
|
459
|
+
def merge(context, override_protected: false)
|
460
|
+
ctx = Context.new(term_definitions: term_definitions, standard_prefixes: options[:standard_prefixes])
|
461
|
+
ctx.context_base = context.context_base || context_base
|
462
|
+
ctx.default_language = context.default_language || default_language
|
463
|
+
ctx.default_direction = context.default_direction || default_direction
|
464
|
+
ctx.vocab = context.vocab || vocab
|
465
|
+
ctx.base = base unless base.nil?
|
466
|
+
unless override_protected
|
467
|
+
ctx.term_definitions.each do |term, definition|
|
468
|
+
next unless definition.protected? && (other = context.term_definitions[term])
|
469
|
+
unless definition == other
|
470
|
+
raise JSON::LD::JsonLdError::ProtectedTermRedefinition, "Attempt to redefine protected term #{term}"
|
471
|
+
end
|
433
472
|
end
|
434
473
|
end
|
435
|
-
end
|
436
474
|
|
437
|
-
|
438
|
-
|
439
|
-
|
475
|
+
# Add term definitions
|
476
|
+
context.term_definitions.each do |term, definition|
|
477
|
+
ctx.term_definitions[term] = definition
|
478
|
+
end
|
479
|
+
ctx
|
440
480
|
end
|
441
|
-
ctx
|
442
|
-
end
|
443
481
|
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
##
|
455
|
-
# Create Term Definition
|
456
|
-
#
|
457
|
-
# Term definitions are created by parsing the information in the given local context for the given term. If the given term is a compact IRI, it may omit an IRI mapping by depending on its prefix having its own term definition. If the prefix is a key in the local context, then its term definition must first be created, through recursion, before continuing. Because a term definition can depend on other term definitions, a mechanism must be used to detect cyclical dependencies. The solution employed here uses a map, defined, that keeps track of whether or not a term has been defined or is currently in the process of being defined. This map is checked before any recursion is attempted.
|
458
|
-
#
|
459
|
-
# After all dependencies for a term have been defined, the rest of the information in the local context for the given term is taken into account, creating the appropriate IRI mapping, container mapping, and type mapping or language mapping for the term.
|
460
|
-
#
|
461
|
-
# @param [Hash] local_context
|
462
|
-
# @param [String] term
|
463
|
-
# @param [Hash] defined
|
464
|
-
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
465
|
-
# @param [Boolean] protected if true, causes all terms to be marked protected
|
466
|
-
# @param [Boolean] override_protected Protected terms may be cleared.
|
467
|
-
# @param [Array<String>] remote_contexts
|
468
|
-
# @param [Boolean] validate_scoped (true).
|
469
|
-
# Validate scoped context, loading if necessary.
|
470
|
-
# If false, do not load scoped contexts.
|
471
|
-
# @raise [JsonLdError]
|
472
|
-
# Represents a cyclical term dependency
|
473
|
-
# @see https://www.w3.org/TR/json-ld11-api/index.html#create-term-definition
|
474
|
-
def create_term_definition(local_context, term, defined,
|
475
|
-
base: nil,
|
476
|
-
override_protected: false,
|
477
|
-
protected: nil,
|
478
|
-
remote_contexts: [],
|
479
|
-
validate_scoped: true)
|
480
|
-
# Expand a string value, unless it matches a keyword
|
481
|
-
log_debug("create_term_definition") {"term = #{term.inspect}"}
|
482
|
-
|
483
|
-
# If defined contains the key term, then the associated value must be true, indicating that the term definition has already been created, so return. Otherwise, a cyclical term definition has been detected, which is an error.
|
484
|
-
case defined[term]
|
485
|
-
when TrueClass then return
|
486
|
-
when nil
|
487
|
-
defined[term] = false
|
488
|
-
else
|
489
|
-
raise JsonLdError::CyclicIRIMapping, "Cyclical term dependency found: #{term.inspect}"
|
490
|
-
end
|
482
|
+
# The following constants are used to reduce object allocations in #create_term_definition below
|
483
|
+
ID_NULL_OBJECT = { '@id' => nil }.freeze
|
484
|
+
NON_TERMDEF_KEYS = Set.new(%w[@base @direction @language @protected @version @vocab]).freeze
|
485
|
+
JSON_LD_10_EXPECTED_KEYS = Set.new(%w[@container @id @language @reverse @type]).freeze
|
486
|
+
JSON_LD_11_EXPECTED_KEYS = Set.new(%w[@context @direction @index @nest @prefix @protected]).freeze
|
487
|
+
JSON_LD_EXPECTED_KEYS = (JSON_LD_10_EXPECTED_KEYS + JSON_LD_11_EXPECTED_KEYS).freeze
|
488
|
+
JSON_LD_10_TYPE_VALUES = Set.new(%w[@id @vocab]).freeze
|
489
|
+
JSON_LD_11_TYPE_VALUES = Set.new(%w[@json @none]).freeze
|
490
|
+
PREFIX_URI_ENDINGS = Set.new(%w(: / ? # [ ] @)).freeze
|
491
491
|
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
#
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
492
|
+
##
|
493
|
+
# Create Term Definition
|
494
|
+
#
|
495
|
+
# Term definitions are created by parsing the information in the given local context for the given term. If the given term is a compact IRI, it may omit an IRI mapping by depending on its prefix having its own term definition. If the prefix is a key in the local context, then its term definition must first be created, through recursion, before continuing. Because a term definition can depend on other term definitions, a mechanism must be used to detect cyclical dependencies. The solution employed here uses a map, defined, that keeps track of whether or not a term has been defined or is currently in the process of being defined. This map is checked before any recursion is attempted.
|
496
|
+
#
|
497
|
+
# After all dependencies for a term have been defined, the rest of the information in the local context for the given term is taken into account, creating the appropriate IRI mapping, container mapping, and type mapping or language mapping for the term.
|
498
|
+
#
|
499
|
+
# @param [Hash] local_context
|
500
|
+
# @param [String] term
|
501
|
+
# @param [Hash] defined
|
502
|
+
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
503
|
+
# @param [Boolean] protected if true, causes all terms to be marked protected
|
504
|
+
# @param [Boolean] override_protected Protected terms may be cleared.
|
505
|
+
# @param [Array<String>] remote_contexts
|
506
|
+
# @param [Boolean] validate_scoped (true).
|
507
|
+
# Validate scoped context, loading if necessary.
|
508
|
+
# If false, do not load scoped contexts.
|
509
|
+
# @raise [JsonLdError]
|
510
|
+
# Represents a cyclical term dependency
|
511
|
+
# @see https://www.w3.org/TR/json-ld11-api/index.html#create-term-definition
|
512
|
+
def create_term_definition(local_context, term, defined,
|
513
|
+
base: nil,
|
514
|
+
override_protected: false,
|
515
|
+
protected: nil,
|
516
|
+
remote_contexts: [],
|
517
|
+
validate_scoped: true)
|
518
|
+
# Expand a string value, unless it matches a keyword
|
519
|
+
# log_debug("create_term_definition") {"term = #{term.inspect}"}
|
520
|
+
|
521
|
+
# If defined contains the key term, then the associated value must be true, indicating that the term definition has already been created, so return. Otherwise, a cyclical term definition has been detected, which is an error.
|
522
|
+
case defined[term]
|
523
|
+
when TrueClass then return
|
524
|
+
when nil
|
525
|
+
defined[term] = false
|
526
|
+
else
|
527
|
+
raise JsonLdError::CyclicIRIMapping, "Cyclical term dependency found: #{term.inspect}"
|
528
|
+
end
|
513
529
|
|
514
|
-
|
530
|
+
# Initialize value to a the value associated with the key term in local context.
|
531
|
+
value = local_context.fetch(term, false)
|
532
|
+
simple_term = value.is_a?(String) || value.nil?
|
533
|
+
|
534
|
+
# Since keywords cannot be overridden, term must not be a keyword. Otherwise, an invalid value has been detected, which is an error.
|
535
|
+
if term == '@type' &&
|
536
|
+
value.is_a?(Hash) &&
|
537
|
+
!value.empty? &&
|
538
|
+
processingMode("json-ld-1.1") &&
|
539
|
+
(value.keys - %w[@container @protected]).empty? &&
|
540
|
+
value.fetch('@container', '@set') == '@set'
|
541
|
+
# thes are the only cases were redefining a keyword is allowed
|
542
|
+
elsif KEYWORDS.include?(term) # TODO: anything that looks like a keyword
|
543
|
+
raise JsonLdError::KeywordRedefinition, "term must not be a keyword: #{term.inspect}" if
|
544
|
+
@options[:validate]
|
545
|
+
elsif term.to_s.match?(/^@[a-zA-Z]+$/) && @options[:validate]
|
546
|
+
warn "Terms beginning with '@' are reserved for future use and ignored: #{term}."
|
547
|
+
return
|
548
|
+
elsif !term_valid?(term) && @options[:validate]
|
549
|
+
raise JsonLdError::InvalidTermDefinition, "term is invalid: #{term.inspect}"
|
550
|
+
end
|
515
551
|
|
516
|
-
|
517
|
-
previous_definition = term_definitions[term]
|
518
|
-
if previous_definition && previous_definition.protected? && !override_protected
|
519
|
-
# Check later to detect identical redefinition
|
520
|
-
else
|
521
|
-
term_definitions.delete(term) if previous_definition
|
522
|
-
end
|
552
|
+
value = { '@id' => value } if simple_term
|
523
553
|
|
524
|
-
|
554
|
+
# Remove any existing term definition for term in active context.
|
555
|
+
previous_definition = term_definitions[term]
|
556
|
+
if previous_definition&.protected? && !override_protected
|
557
|
+
# Check later to detect identical redefinition
|
558
|
+
elsif previous_definition
|
559
|
+
term_definitions.delete(term)
|
560
|
+
end
|
525
561
|
|
526
|
-
|
527
|
-
|
528
|
-
|
562
|
+
unless value.is_a?(Hash)
|
563
|
+
raise JsonLdError::InvalidTermDefinition,
|
564
|
+
"Term definition for #{term.inspect} is an #{value.class} on term #{term.inspect}"
|
565
|
+
end
|
529
566
|
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
end
|
567
|
+
# log_debug("") {"Hash[#{term.inspect}] = #{value.inspect}"}
|
568
|
+
definition = TermDefinition.new(term)
|
569
|
+
definition.simple = simple_term
|
534
570
|
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
571
|
+
expected_keys = case processingMode
|
572
|
+
when "json-ld-1.0" then JSON_LD_10_EXPECTED_KEYS
|
573
|
+
else JSON_LD_EXPECTED_KEYS
|
574
|
+
end
|
539
575
|
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
576
|
+
# Any of these keys cause us to process as json-ld-1.1, unless otherwise set
|
577
|
+
if processingMode.nil? && value.any? { |key, _| !JSON_LD_11_EXPECTED_KEYS.include?(key) }
|
578
|
+
processingMode('json-ld-11')
|
579
|
+
end
|
544
580
|
|
545
|
-
|
546
|
-
|
581
|
+
if value.any? { |key, _| !expected_keys.include?(key) }
|
582
|
+
extra_keys = value.keys - expected_keys.to_a
|
583
|
+
raise JsonLdError::InvalidTermDefinition,
|
584
|
+
"Term definition for #{term.inspect} has unexpected keys: #{extra_keys.join(', ')}"
|
585
|
+
end
|
547
586
|
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
type
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
587
|
+
# Potentially note that the term is protected
|
588
|
+
definition.protected = value.fetch('@protected', protected)
|
589
|
+
|
590
|
+
if value.key?('@type')
|
591
|
+
type = value['@type']
|
592
|
+
# SPEC FIXME: @type may be nil
|
593
|
+
type = case type
|
594
|
+
when nil
|
595
|
+
type
|
596
|
+
when String
|
597
|
+
begin
|
598
|
+
expand_iri(type, vocab: true, documentRelative: false, local_context: local_context, defined: defined)
|
599
|
+
rescue JsonLdError::InvalidIRIMapping
|
600
|
+
raise JsonLdError::InvalidTypeMapping,
|
601
|
+
"invalid mapping for '@type': #{type.inspect} on term #{term.inspect}"
|
602
|
+
end
|
603
|
+
else
|
604
|
+
:error
|
559
605
|
end
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
606
|
+
if JSON_LD_11_TYPE_VALUES.include?(type) && processingMode('json-ld-1.1')
|
607
|
+
# This is okay and used in compaction in 1.1
|
608
|
+
elsif !JSON_LD_10_TYPE_VALUES.include?(type) && !(type.is_a?(RDF::URI) && type.absolute?)
|
609
|
+
raise JsonLdError::InvalidTypeMapping,
|
610
|
+
"unknown mapping for '@type': #{type.inspect} on term #{term.inspect}"
|
611
|
+
end
|
612
|
+
# log_debug("") {"type_mapping: #{type.inspect}"}
|
613
|
+
definition.type_mapping = type
|
567
614
|
end
|
568
|
-
#log_debug("") {"type_mapping: #{type.inspect}"}
|
569
|
-
definition.type_mapping = type
|
570
|
-
end
|
571
615
|
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
raise JsonLdError::InvalidIRIMapping, "expected value of @reverse to be a string: #{value['@reverse'].inspect} on term #{term.inspect}" unless
|
576
|
-
value['@reverse'].is_a?(String)
|
616
|
+
if value.key?('@reverse')
|
617
|
+
raise JsonLdError::InvalidReverseProperty, "unexpected key in #{value.inspect} on term #{term.inspect}" if
|
618
|
+
value.key?('@id') || value.key?('@nest')
|
577
619
|
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
620
|
+
unless value['@reverse'].is_a?(String)
|
621
|
+
raise JsonLdError::InvalidIRIMapping,
|
622
|
+
"expected value of @reverse to be a string: #{value['@reverse'].inspect} on term #{term.inspect}"
|
623
|
+
end
|
624
|
+
|
625
|
+
if value['@reverse'].to_s.match?(/^@[a-zA-Z]+$/) && @options[:validate]
|
626
|
+
warn "Values beginning with '@' are reserved for future use and ignored: #{value['@reverse']}."
|
627
|
+
return
|
628
|
+
end
|
582
629
|
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
630
|
+
# 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.
|
631
|
+
definition.id = expand_iri(value['@reverse'],
|
632
|
+
vocab: true,
|
633
|
+
local_context: local_context,
|
634
|
+
defined: defined)
|
635
|
+
unless definition.id.is_a?(RDF::Node) || (definition.id.is_a?(RDF::URI) && definition.id.absolute?)
|
636
|
+
raise JsonLdError::InvalidIRIMapping,
|
637
|
+
"non-absolute @reverse IRI: #{definition.id} on term #{term.inspect}"
|
638
|
+
end
|
639
|
+
|
640
|
+
if term[1..].to_s.include?(':') && (term_iri = expand_iri(term)) != definition.id
|
641
|
+
raise JsonLdError::InvalidIRIMapping, "term #{term} expands to #{definition.id}, not #{term_iri}"
|
642
|
+
end
|
643
|
+
|
644
|
+
if @options[:validate] && processingMode('json-ld-1.1') && definition.id.to_s.start_with?("_:")
|
645
|
+
warn "[DEPRECATION] Blank Node terms deprecated in JSON-LD 1.1."
|
646
|
+
end
|
647
|
+
|
648
|
+
# 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.
|
649
|
+
if value.key?('@container')
|
650
|
+
container = value['@container']
|
651
|
+
unless container.is_a?(String) && ['@set', '@index'].include?(container)
|
652
|
+
raise JsonLdError::InvalidReverseProperty,
|
653
|
+
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}"
|
654
|
+
end
|
655
|
+
definition.container_mapping = check_container(container, local_context, defined, term)
|
656
|
+
end
|
657
|
+
definition.reverse_property = true
|
658
|
+
elsif value.key?('@id') && value['@id'].nil?
|
659
|
+
# Allowed to reserve a null term, which may be protected
|
660
|
+
elsif value.key?('@id') && value['@id'] != term
|
661
|
+
unless value['@id'].is_a?(String)
|
662
|
+
raise JsonLdError::InvalidIRIMapping,
|
663
|
+
"expected value of @id to be a string: #{value['@id'].inspect} on term #{term.inspect}"
|
664
|
+
end
|
665
|
+
|
666
|
+
if !KEYWORDS.include?(value['@id'].to_s) && value['@id'].to_s.match?(/^@[a-zA-Z]+$/) && @options[:validate]
|
667
|
+
warn "Values beginning with '@' are reserved for future use and ignored: #{value['@id']}."
|
668
|
+
return
|
669
|
+
end
|
670
|
+
|
671
|
+
definition.id = expand_iri(value['@id'],
|
672
|
+
vocab: true,
|
673
|
+
local_context: local_context,
|
674
|
+
defined: defined)
|
675
|
+
raise JsonLdError::InvalidKeywordAlias, "expected value of @id to not be @context on term #{term.inspect}" if
|
676
|
+
definition.id == '@context'
|
677
|
+
|
678
|
+
if term.match?(%r{(?::[^:])|/})
|
679
|
+
term_iri = expand_iri(term,
|
680
|
+
vocab: true,
|
681
|
+
local_context: local_context,
|
682
|
+
defined: defined.merge(term => true))
|
683
|
+
if term_iri != definition.id
|
684
|
+
raise JsonLdError::InvalidIRIMapping, "term #{term} expands to #{definition.id}, not #{term_iri}"
|
685
|
+
end
|
686
|
+
end
|
687
|
+
|
688
|
+
if @options[:validate] && processingMode('json-ld-1.1') && definition.id.to_s.start_with?("_:")
|
689
|
+
warn "[DEPRECATION] Blank Node terms deprecated in JSON-LD 1.1."
|
690
|
+
end
|
691
|
+
|
692
|
+
# If id ends with a gen-delim, it may be used as a prefix for simple terms
|
693
|
+
definition.prefix = true if !term.include?(':') &&
|
694
|
+
simple_term &&
|
695
|
+
(definition.id.to_s.end_with?(':', '/', '?', '#', '[', ']',
|
696
|
+
'@') || definition.id.to_s.start_with?('_:'))
|
697
|
+
elsif term[1..].include?(':')
|
698
|
+
# 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.
|
699
|
+
prefix, suffix = term.split(':', 2)
|
700
|
+
create_term_definition(local_context, prefix, defined, protected: protected) if local_context.key?(prefix)
|
701
|
+
|
702
|
+
definition.id = if (td = term_definitions[prefix])
|
703
|
+
# 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.
|
704
|
+
td.id + suffix
|
705
|
+
else
|
706
|
+
# Otherwise, term is an absolute IRI. Set the IRI mapping for definition to term
|
707
|
+
term
|
708
|
+
end
|
709
|
+
# log_debug("") {"=> #{definition.id}"}
|
710
|
+
elsif term.include?('/')
|
711
|
+
# If term is a relative IRI
|
712
|
+
definition.id = expand_iri(term, vocab: true)
|
713
|
+
raise JsonLdError::InvalidKeywordAlias, "expected term to expand to an absolute IRI #{term.inspect}" unless
|
714
|
+
definition.id.absolute?
|
715
|
+
elsif KEYWORDS.include?(term)
|
716
|
+
# This should only happen for @type when @container is @set
|
717
|
+
definition.id = term
|
718
|
+
else
|
719
|
+
# Otherwise, active context must have a vocabulary mapping, otherwise an invalid value has been detected, which is an error. Set the IRI mapping for definition to the result of concatenating the value associated with the vocabulary mapping and term.
|
720
|
+
unless vocab
|
721
|
+
raise JsonLdError::InvalidIRIMapping,
|
722
|
+
"relative term definition without vocab: #{term} on term #{term.inspect}"
|
723
|
+
end
|
590
724
|
|
591
|
-
|
592
|
-
|
725
|
+
definition.id = vocab + term
|
726
|
+
# log_debug("") {"=> #{definition.id}"}
|
593
727
|
end
|
594
728
|
|
595
|
-
|
729
|
+
@iri_to_term[definition.id] = term if simple_term && definition.id
|
596
730
|
|
597
|
-
# 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.
|
598
731
|
if value.key?('@container')
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
definition.container_mapping
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
if !KEYWORDS.include?(value['@id'].to_s) && value['@id'].to_s.match?(/^@[a-zA-Z]+$/) && @options[:validate]
|
613
|
-
warn "Values beginning with '@' are reserved for future use and ignored: #{value['@id']}."
|
614
|
-
return
|
732
|
+
# log_debug("") {"container_mapping: #{value['@container'].inspect}"}
|
733
|
+
definition.container_mapping = check_container(value['@container'], local_context, defined, term)
|
734
|
+
|
735
|
+
# If @container includes @type
|
736
|
+
if definition.container_mapping.include?('@type')
|
737
|
+
# If definition does not have @type, set @type to @id
|
738
|
+
definition.type_mapping ||= '@id'
|
739
|
+
# If definition includes @type with a value other than @id or @vocab, an illegal type mapping error has been detected
|
740
|
+
unless CONTEXT_TYPE_ID_VOCAB.include?(definition.type_mapping)
|
741
|
+
raise JsonLdError::InvalidTypeMapping, "@container: @type requires @type to be @id or @vocab"
|
742
|
+
end
|
743
|
+
end
|
615
744
|
end
|
616
745
|
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
746
|
+
if value.key?('@index')
|
747
|
+
# property-based indexing
|
748
|
+
unless definition.container_mapping.include?('@index')
|
749
|
+
raise JsonLdError::InvalidTermDefinition,
|
750
|
+
"@index without @index in @container: #{value['@index']} on term #{term.inspect}"
|
751
|
+
end
|
752
|
+
unless value['@index'].is_a?(String) && !value['@index'].start_with?('@')
|
753
|
+
raise JsonLdError::InvalidTermDefinition,
|
754
|
+
"@index must expand to an IRI: #{value['@index']} on term #{term.inspect}"
|
755
|
+
end
|
623
756
|
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
757
|
+
definition.index = value['@index'].to_s
|
758
|
+
end
|
759
|
+
|
760
|
+
if value.key?('@context')
|
761
|
+
begin
|
762
|
+
new_ctx = parse(value['@context'],
|
763
|
+
base: base,
|
764
|
+
override_protected: true,
|
765
|
+
remote_contexts: remote_contexts,
|
766
|
+
validate_scoped: false)
|
767
|
+
# Record null context in array form
|
768
|
+
definition.context = case value['@context']
|
769
|
+
when String then new_ctx.context_base
|
770
|
+
when nil then [nil]
|
771
|
+
else value['@context']
|
772
|
+
end
|
773
|
+
# log_debug("") {"context: #{definition.context.inspect}"}
|
774
|
+
rescue JsonLdError => e
|
775
|
+
raise JsonLdError::InvalidScopedContext,
|
776
|
+
"Term definition for #{term.inspect} contains illegal value for @context: #{e.message}"
|
631
777
|
end
|
632
778
|
end
|
633
779
|
|
634
|
-
|
780
|
+
if value.key?('@language')
|
781
|
+
language = value['@language']
|
782
|
+
language = case value['@language']
|
783
|
+
when String
|
784
|
+
# Warn on an invalid language tag, unless :validate is true, in which case it's an error
|
785
|
+
unless /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/.match?(value['@language'])
|
786
|
+
warn "@language must be valid BCP47: #{value['@language'].inspect}"
|
787
|
+
end
|
788
|
+
options[:lowercaseLanguage] ? value['@language'].downcase : value['@language']
|
789
|
+
when nil
|
790
|
+
nil
|
791
|
+
else
|
792
|
+
raise JsonLdError::InvalidLanguageMapping,
|
793
|
+
"language must be null or a string, was #{value['@language'].inspect}} on term #{term.inspect}"
|
794
|
+
end
|
795
|
+
# log_debug("") {"language_mapping: #{language.inspect}"}
|
796
|
+
definition.language_mapping = language || false
|
797
|
+
end
|
635
798
|
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
799
|
+
if value.key?('@direction')
|
800
|
+
direction = value['@direction']
|
801
|
+
unless direction.nil? || %w[
|
802
|
+
ltr rtl
|
803
|
+
].include?(direction)
|
804
|
+
raise JsonLdError::InvalidBaseDirection,
|
805
|
+
"direction must be null, 'ltr', or 'rtl', was #{language.inspect}} on term #{term.inspect}"
|
806
|
+
end
|
644
807
|
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
# This should only happen for @type when @container is @set
|
660
|
-
definition.id = term
|
661
|
-
else
|
662
|
-
# Otherwise, active context must have a vocabulary mapping, otherwise an invalid value has been detected, which is an error. Set the IRI mapping for definition to the result of concatenating the value associated with the vocabulary mapping and term.
|
663
|
-
raise JsonLdError::InvalidIRIMapping, "relative term definition without vocab: #{term} on term #{term.inspect}" unless vocab
|
664
|
-
definition.id = vocab + term
|
665
|
-
log_debug("") {"=> #{definition.id}"}
|
666
|
-
end
|
808
|
+
# log_debug("") {"direction_mapping: #{direction.inspect}"}
|
809
|
+
definition.direction_mapping = direction || false
|
810
|
+
end
|
811
|
+
|
812
|
+
if value.key?('@nest')
|
813
|
+
nest = value['@nest']
|
814
|
+
unless nest.is_a?(String)
|
815
|
+
raise JsonLdError::InvalidNestValue,
|
816
|
+
"nest must be a string, was #{nest.inspect}} on term #{term.inspect}"
|
817
|
+
end
|
818
|
+
if nest.match?(/^@[a-zA-Z]+$/) && nest != '@nest'
|
819
|
+
raise JsonLdError::InvalidNestValue,
|
820
|
+
"nest must not be a keyword other than @nest, was #{nest.inspect}} on term #{term.inspect}"
|
821
|
+
end
|
667
822
|
|
668
|
-
|
823
|
+
# log_debug("") {"nest: #{nest.inspect}"}
|
824
|
+
definition.nest = nest
|
825
|
+
end
|
826
|
+
|
827
|
+
if value.key?('@prefix')
|
828
|
+
if term.match?(%r{:|/})
|
829
|
+
raise JsonLdError::InvalidTermDefinition,
|
830
|
+
"@prefix used on compact or relative IRI term #{term.inspect}"
|
831
|
+
end
|
669
832
|
|
670
|
-
|
671
|
-
|
672
|
-
|
833
|
+
case pfx = value['@prefix']
|
834
|
+
when TrueClass, FalseClass
|
835
|
+
definition.prefix = pfx
|
836
|
+
else
|
837
|
+
raise JsonLdError::InvalidPrefixValue, "unknown value for '@prefix': #{pfx.inspect} on term #{term.inspect}"
|
838
|
+
end
|
673
839
|
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
definition.type_mapping ||= '@id'
|
678
|
-
# If definition includes @type with a value other than @id or @vocab, an illegal type mapping error has been detected
|
679
|
-
if !CONTEXT_TYPE_ID_VOCAB.include?(definition.type_mapping)
|
680
|
-
raise JsonLdError::InvalidTypeMapping, "@container: @type requires @type to be @id or @vocab"
|
840
|
+
if pfx && KEYWORDS.include?(definition.id.to_s)
|
841
|
+
raise JsonLdError::InvalidTermDefinition,
|
842
|
+
"keywords may not be used as prefixes"
|
681
843
|
end
|
682
844
|
end
|
845
|
+
|
846
|
+
if previous_definition&.protected? && definition != previous_definition && !override_protected
|
847
|
+
definition = previous_definition
|
848
|
+
raise JSON::LD::JsonLdError::ProtectedTermRedefinition, "Attempt to redefine protected term #{term}"
|
849
|
+
end
|
850
|
+
|
851
|
+
term_definitions[term] = definition
|
852
|
+
defined[term] = true
|
683
853
|
end
|
684
854
|
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
855
|
+
##
|
856
|
+
# Initial context, without mappings, vocab or default language
|
857
|
+
#
|
858
|
+
# @return [Boolean]
|
859
|
+
def empty?
|
860
|
+
@term_definitions.empty? && vocab.nil? && default_language.nil?
|
690
861
|
end
|
691
862
|
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
rescue JsonLdError => e
|
707
|
-
raise JsonLdError::InvalidScopedContext, "Term definition for #{term.inspect} contains illegal value for @context: #{e.message}"
|
863
|
+
# @param [String] value must be an absolute IRI
|
864
|
+
def base=(value, **_options)
|
865
|
+
if value
|
866
|
+
unless value.is_a?(String) || value.is_a?(RDF::URI)
|
867
|
+
raise JsonLdError::InvalidBaseIRI,
|
868
|
+
"@base must be a string: #{value.inspect}"
|
869
|
+
end
|
870
|
+
|
871
|
+
value = RDF::URI(value)
|
872
|
+
value = @base.join(value) if @base && value.relative?
|
873
|
+
# still might be relative to document
|
874
|
+
@base = value
|
875
|
+
else
|
876
|
+
@base = false
|
708
877
|
end
|
709
878
|
end
|
710
879
|
|
711
|
-
|
712
|
-
|
713
|
-
|
880
|
+
# @param [String] value
|
881
|
+
def default_language=(value, **options)
|
882
|
+
@default_language = case value
|
714
883
|
when String
|
715
884
|
# Warn on an invalid language tag, unless :validate is true, in which case it's an error
|
716
|
-
|
717
|
-
warn "@language must be valid BCP47: #{value
|
885
|
+
unless /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/.match?(value)
|
886
|
+
warn "@language must be valid BCP47: #{value.inspect}"
|
718
887
|
end
|
719
|
-
options[:lowercaseLanguage] ? value
|
888
|
+
options[:lowercaseLanguage] ? value.downcase : value
|
720
889
|
when nil
|
721
890
|
nil
|
722
891
|
else
|
723
|
-
raise JsonLdError::
|
892
|
+
raise JsonLdError::InvalidDefaultLanguage, "@language must be a string: #{value.inspect}"
|
724
893
|
end
|
725
|
-
#log_debug("") {"language_mapping: #{language.inspect}"}
|
726
|
-
definition.language_mapping = language || false
|
727
894
|
end
|
728
895
|
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
896
|
+
# @param [String] value
|
897
|
+
def default_direction=(value, **_options)
|
898
|
+
@default_direction = if value
|
899
|
+
unless %w[
|
900
|
+
ltr rtl
|
901
|
+
].include?(value)
|
902
|
+
raise JsonLdError::InvalidBaseDirection,
|
903
|
+
"@direction must be one or 'ltr', or 'rtl': #{value.inspect}"
|
904
|
+
end
|
735
905
|
|
736
|
-
|
737
|
-
|
738
|
-
raise JsonLdError::InvalidNestValue, "nest must be a string, was #{nest.inspect}} on term #{term.inspect}" unless nest.is_a?(String)
|
739
|
-
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'
|
740
|
-
#log_debug("") {"nest: #{nest.inspect}"}
|
741
|
-
definition.nest = nest
|
906
|
+
value
|
907
|
+
end
|
742
908
|
end
|
743
909
|
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
910
|
+
##
|
911
|
+
# Retrieve, or check processing mode.
|
912
|
+
#
|
913
|
+
# * With no arguments, retrieves the current set processingMode.
|
914
|
+
# * 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"
|
915
|
+
# * If expecting 1.1, and not set, it has the side-effect of setting mode to json-ld-1.1.
|
916
|
+
#
|
917
|
+
# @param [String, Number] expected (nil)
|
918
|
+
# @return [String]
|
919
|
+
def processingMode(expected = nil)
|
920
|
+
case expected
|
921
|
+
when 1.0, 'json-ld-1.0'
|
922
|
+
@processingMode == 'json-ld-1.0'
|
923
|
+
when 1.1, 'json-ld-1.1'
|
924
|
+
@processingMode.nil? || @processingMode == 'json-ld-1.1'
|
925
|
+
when nil
|
926
|
+
@processingMode || 'json-ld-1.1'
|
749
927
|
else
|
750
|
-
|
928
|
+
false
|
751
929
|
end
|
752
|
-
|
753
|
-
raise JsonLdError::InvalidTermDefinition, "keywords may not be used as prefixes" if pfx && KEYWORDS.include?(definition.id.to_s)
|
754
930
|
end
|
755
931
|
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
932
|
+
##
|
933
|
+
# Set processing mode.
|
934
|
+
#
|
935
|
+
# * 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"
|
936
|
+
#
|
937
|
+
# 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.
|
938
|
+
# If processingMode has been set, and it is not "json-ld-1.1", a "processing mode conflict" has been detecting, and processing is aborted.
|
939
|
+
#
|
940
|
+
# @param [String, Number] value
|
941
|
+
# @return [String]
|
942
|
+
# @raise [JsonLdError::ProcessingModeConflict]
|
943
|
+
def processingMode=(value = nil, **_options)
|
944
|
+
value = "json-ld-1.1" if value == 1.1
|
945
|
+
case value
|
946
|
+
when "json-ld-1.0", "json-ld-1.1"
|
947
|
+
if @processingMode && @processingMode != value
|
948
|
+
raise JsonLdError::ProcessingModeConflict, "#{value} not compatible with #{@processingMode}"
|
949
|
+
end
|
760
950
|
|
761
|
-
|
762
|
-
|
763
|
-
|
951
|
+
@processingMode = value
|
952
|
+
else
|
953
|
+
raise JsonLdError::InvalidVersionValue, value.inspect
|
954
|
+
end
|
955
|
+
end
|
764
956
|
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
957
|
+
# 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.
|
958
|
+
# @param [String] value must be an absolute IRI
|
959
|
+
def vocab=(value, **_options)
|
960
|
+
@vocab = case value
|
961
|
+
when /_:/
|
962
|
+
# BNode vocab is deprecated
|
963
|
+
if @options[:validate] && processingMode("json-ld-1.1")
|
964
|
+
warn "[DEPRECATION] Blank Node vocabularies deprecated in JSON-LD 1.1."
|
965
|
+
end
|
966
|
+
value
|
967
|
+
when String, RDF::URI
|
968
|
+
if RDF::URI(value.to_s).relative? && processingMode("json-ld-1.0")
|
969
|
+
raise JsonLdError::InvalidVocabMapping, "@vocab must be an absolute IRI in 1.0 mode: #{value.inspect}"
|
970
|
+
end
|
772
971
|
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
# still might be relative to document
|
780
|
-
@base = value
|
781
|
-
else
|
782
|
-
@base = false
|
972
|
+
expand_iri(value.to_s, vocab: true, documentRelative: true)
|
973
|
+
when nil
|
974
|
+
nil
|
975
|
+
else
|
976
|
+
raise JsonLdError::InvalidVocabMapping, "@vocab must be an IRI: #{value.inspect}"
|
977
|
+
end
|
783
978
|
end
|
784
979
|
|
785
|
-
|
980
|
+
# Set propagation
|
981
|
+
# @note: by the time this is called, the work has already been done.
|
982
|
+
#
|
983
|
+
# @param [Boolean] value
|
984
|
+
def propagate=(value, **_options)
|
985
|
+
if processingMode("json-ld-1.0")
|
986
|
+
raise JsonLdError::InvalidContextEntry,
|
987
|
+
"@propagate may only be set in 1.1 mode"
|
988
|
+
end
|
786
989
|
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
when String
|
791
|
-
# Warn on an invalid language tag, unless :validate is true, in which case it's an error
|
792
|
-
if value !~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/
|
793
|
-
warn "@language must be valid BCP47: #{value.inspect}"
|
990
|
+
unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
991
|
+
raise JsonLdError::InvalidPropagateValue,
|
992
|
+
"@propagate must be boolean valued: #{value.inspect}"
|
794
993
|
end
|
795
|
-
options[:lowercaseLanguage] ? value.downcase : value
|
796
|
-
when nil
|
797
|
-
nil
|
798
|
-
else
|
799
|
-
raise JsonLdError::InvalidDefaultLanguage, "@language must be a string: #{value.inspect}"
|
800
|
-
end
|
801
|
-
end
|
802
994
|
|
803
|
-
# @param [String] value
|
804
|
-
def default_direction=(value, **options)
|
805
|
-
@default_direction = if value
|
806
|
-
raise JsonLdError::InvalidBaseDirection, "@direction must be one or 'ltr', or 'rtl': #{value.inspect}" unless %w(ltr rtl).include?(value)
|
807
995
|
value
|
808
|
-
else
|
809
|
-
nil
|
810
996
|
end
|
811
|
-
end
|
812
997
|
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
998
|
+
##
|
999
|
+
# Generate @context
|
1000
|
+
#
|
1001
|
+
# If a context was supplied in global options, use that, otherwise, generate one
|
1002
|
+
# from this representation.
|
1003
|
+
#
|
1004
|
+
# @param [Array, Hash, Context, IO, StringIO] provided_context (nil)
|
1005
|
+
# Original context to use, if available
|
1006
|
+
# @param [Hash{Symbol => Object}] options ({})
|
1007
|
+
# @return [Hash]
|
1008
|
+
def serialize(provided_context: nil, **_options)
|
1009
|
+
# log_debug("serlialize: generate context")
|
1010
|
+
# log_debug("") {"=> context: #{inspect}"}
|
1011
|
+
use_context = case provided_context
|
1012
|
+
when String, RDF::URI
|
1013
|
+
# log_debug "serlialize: reuse context: #{provided_context.inspect}"
|
1014
|
+
provided_context.to_s
|
1015
|
+
when Hash
|
1016
|
+
# log_debug "serlialize: reuse context: #{provided_context.inspect}"
|
1017
|
+
# If it has an @context entry use it, otherwise it is assumed to be the body of a context
|
1018
|
+
provided_context.fetch('@context', provided_context)
|
1019
|
+
when Array
|
1020
|
+
# log_debug "serlialize: reuse context: #{provided_context.inspect}"
|
1021
|
+
provided_context
|
1022
|
+
when IO, StringIO
|
1023
|
+
load_context(provided_context, **@options).fetch('@context', {})
|
1024
|
+
else
|
1025
|
+
ctx = {}
|
1026
|
+
ctx['@version'] = 1.1 if @processingMode == 'json-ld-1.1'
|
1027
|
+
ctx['@base'] = base.to_s if base
|
1028
|
+
ctx['@direction'] = default_direction.to_s if default_direction
|
1029
|
+
ctx['@language'] = default_language.to_s if default_language
|
1030
|
+
ctx['@vocab'] = vocab.to_s if vocab
|
1031
|
+
|
1032
|
+
# Term Definitions
|
1033
|
+
term_definitions.each do |term, defn|
|
1034
|
+
ctx[term] = defn.to_context_definition(self)
|
1035
|
+
end
|
1036
|
+
ctx
|
1037
|
+
end
|
834
1038
|
|
835
|
-
|
836
|
-
|
837
|
-
#
|
838
|
-
# * 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"
|
839
|
-
#
|
840
|
-
# 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.
|
841
|
-
# If processingMode has been set, and it is not "json-ld-1.1", a "processing mode conflict" has been detecting, and processing is aborted.
|
842
|
-
#
|
843
|
-
# @param [String, Number] value
|
844
|
-
# @return [String]
|
845
|
-
# @raise [JsonLdError::ProcessingModeConflict]
|
846
|
-
def processingMode=(value = nil, **options)
|
847
|
-
value = "json-ld-1.1" if value == 1.1
|
848
|
-
case value
|
849
|
-
when "json-ld-1.0", "json-ld-1.1"
|
850
|
-
if @processingMode && @processingMode != value
|
851
|
-
raise JsonLdError::ProcessingModeConflict, "#{value} not compatible with #{@processingMode}"
|
852
|
-
end
|
853
|
-
@processingMode = value
|
854
|
-
else
|
855
|
-
raise JsonLdError::InvalidVersionValue, value.inspect
|
1039
|
+
# Return hash with @context, or empty
|
1040
|
+
use_context.nil? || use_context.empty? ? {} : { '@context' => use_context }
|
856
1041
|
end
|
857
|
-
end
|
858
1042
|
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
@
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
1043
|
+
##
|
1044
|
+
# Build a context from an RDF::Vocabulary definition.
|
1045
|
+
#
|
1046
|
+
# @example building from an external vocabulary definition
|
1047
|
+
#
|
1048
|
+
# g = RDF::Graph.load("http://schema.org/docs/schema_org_rdfa.html")
|
1049
|
+
#
|
1050
|
+
# context = JSON::LD::Context.new.from_vocabulary(g,
|
1051
|
+
# vocab: "http://schema.org/",
|
1052
|
+
# prefixes: {schema: "http://schema.org/"},
|
1053
|
+
# language: "en")
|
1054
|
+
#
|
1055
|
+
# @param [RDF::Queryable] graph
|
1056
|
+
#
|
1057
|
+
# @note requires rdf/vocab gem.
|
1058
|
+
#
|
1059
|
+
# @return [self]
|
1060
|
+
def from_vocabulary(graph)
|
1061
|
+
require 'rdf/vocab' unless RDF.const_defined?(:Vocab)
|
1062
|
+
statements = {}
|
1063
|
+
ranges = {}
|
1064
|
+
|
1065
|
+
# Add term definitions for each class and property not in schema:, and
|
1066
|
+
# for those properties having an object range
|
1067
|
+
graph.each do |statement|
|
1068
|
+
next if statement.subject.node?
|
1069
|
+
|
1070
|
+
(statements[statement.subject] ||= []) << statement
|
1071
|
+
|
1072
|
+
# Keep track of predicate ranges
|
1073
|
+
if [RDF::RDFS.range, RDF::Vocab::SCHEMA.rangeIncludes].include?(statement.predicate)
|
1074
|
+
(ranges[statement.subject] ||= []) << statement.object
|
1075
|
+
end
|
870
1076
|
end
|
871
|
-
expand_iri(value.to_s, vocab: true, documentRelative: true)
|
872
|
-
when nil
|
873
|
-
nil
|
874
|
-
else
|
875
|
-
raise JsonLdError::InvalidVocabMapping, "@vocab must be an IRI: #{value.inspect}"
|
876
|
-
end
|
877
|
-
end
|
878
1077
|
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
1078
|
+
# Add term definitions for each class and property not in vocab, and
|
1079
|
+
# for those properties having an object range
|
1080
|
+
statements.each do |subject, values|
|
1081
|
+
types = values.each_with_object([]) { |v, memo| memo << v.object if v.predicate == RDF.type }
|
1082
|
+
is_property = types.any? { |t| t.to_s.include?("Property") }
|
1083
|
+
|
1084
|
+
term = subject.to_s.split(%r{[/\#]}).last
|
1085
|
+
|
1086
|
+
if is_property
|
1087
|
+
prop_ranges = ranges.fetch(subject, [])
|
1088
|
+
# If any range is empty or member of range includes rdfs:Literal or schema:Text
|
1089
|
+
next if (vocab && prop_ranges.empty?) ||
|
1090
|
+
prop_ranges.include?(RDF::Vocab::SCHEMA.Text) ||
|
1091
|
+
prop_ranges.include?(RDF::RDFS.Literal)
|
1092
|
+
|
1093
|
+
td = term_definitions[term] = TermDefinition.new(term, id: subject.to_s)
|
1094
|
+
|
1095
|
+
# Set context typing based on first element in range
|
1096
|
+
case r = prop_ranges.first
|
1097
|
+
when RDF::XSD.string
|
1098
|
+
td.language_mapping = false if default_language
|
1099
|
+
# FIXME: text direction
|
1100
|
+
when RDF::XSD.boolean, RDF::Vocab::SCHEMA.Boolean, RDF::XSD.date, RDF::Vocab::SCHEMA.Date,
|
1101
|
+
RDF::XSD.dateTime, RDF::Vocab::SCHEMA.DateTime, RDF::XSD.time, RDF::Vocab::SCHEMA.Time,
|
1102
|
+
RDF::XSD.duration, RDF::Vocab::SCHEMA.Duration, RDF::XSD.decimal, RDF::Vocab::SCHEMA.Number,
|
1103
|
+
RDF::XSD.float, RDF::Vocab::SCHEMA.Float, RDF::XSD.integer, RDF::Vocab::SCHEMA.Integer
|
1104
|
+
td.type_mapping = r
|
1105
|
+
td.simple = false
|
1106
|
+
else
|
1107
|
+
# It's an object range (includes schema:URL)
|
1108
|
+
td.type_mapping = '@id'
|
1109
|
+
end
|
1110
|
+
else
|
1111
|
+
# Ignore if there's a default voabulary and this is not a property
|
1112
|
+
next if vocab && subject.to_s.start_with?(vocab)
|
888
1113
|
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
# If a context was supplied in global options, use that, otherwise, generate one
|
893
|
-
# from this representation.
|
894
|
-
#
|
895
|
-
# @param [Array, Hash, Context, IO, StringIO] provided_context (nil)
|
896
|
-
# Original context to use, if available
|
897
|
-
# @param [Hash{Symbol => Object}] options ({})
|
898
|
-
# @return [Hash]
|
899
|
-
def serialize(provided_context: nil, **options)
|
900
|
-
#log_debug("serlialize: generate context")
|
901
|
-
#log_debug("") {"=> context: #{inspect}"}
|
902
|
-
use_context = case provided_context
|
903
|
-
when String, RDF::URI
|
904
|
-
#log_debug "serlialize: reuse context: #{provided_context.inspect}"
|
905
|
-
provided_context.to_s
|
906
|
-
when Hash
|
907
|
-
#log_debug "serlialize: reuse context: #{provided_context.inspect}"
|
908
|
-
# If it has an @context entry use it, otherwise it is assumed to be the body of a context
|
909
|
-
provided_context.fetch('@context', provided_context)
|
910
|
-
when Array
|
911
|
-
#log_debug "serlialize: reuse context: #{provided_context.inspect}"
|
912
|
-
provided_context
|
913
|
-
when IO, StringIO
|
914
|
-
provided_context.rewind
|
915
|
-
JSON.load(provided_context).fetch('@context', {})
|
916
|
-
else
|
917
|
-
ctx = {}
|
918
|
-
ctx['@version'] = 1.1 if @processingMode == 'json-ld-1.1'
|
919
|
-
ctx['@base'] = base.to_s if base
|
920
|
-
ctx['@direction'] = default_direction.to_s if default_direction
|
921
|
-
ctx['@language'] = default_language.to_s if default_language
|
922
|
-
ctx['@vocab'] = vocab.to_s if vocab
|
923
|
-
|
924
|
-
# Term Definitions
|
925
|
-
term_definitions.each do |term, defn|
|
926
|
-
ctx[term] = defn.to_context_definition(self)
|
1114
|
+
# otherwise, create a term definition
|
1115
|
+
td = term_definitions[term] = TermDefinition.new(term, id: subject.to_s)
|
1116
|
+
end
|
927
1117
|
end
|
928
|
-
|
1118
|
+
|
1119
|
+
self
|
929
1120
|
end
|
930
1121
|
|
931
|
-
#
|
932
|
-
|
933
|
-
|
1122
|
+
# Set term mapping
|
1123
|
+
#
|
1124
|
+
# @param [#to_s] term
|
1125
|
+
# @param [RDF::URI, String, nil] value
|
1126
|
+
#
|
1127
|
+
# @return [TermDefinition]
|
1128
|
+
def set_mapping(term, value)
|
1129
|
+
# log_debug("") {"map #{term.inspect} to #{value.inspect}"}
|
1130
|
+
term = term.to_s
|
1131
|
+
term_definitions[term] =
|
1132
|
+
TermDefinition.new(term, id: value, simple: true, prefix: value.to_s.end_with?(*PREFIX_URI_ENDINGS))
|
1133
|
+
term_definitions[term].simple = true
|
934
1134
|
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
# g = RDF::Graph.load("http://schema.org/docs/schema_org_rdfa.html")
|
941
|
-
#
|
942
|
-
# context = JSON::LD::Context.new.from_vocabulary(g,
|
943
|
-
# vocab: "http://schema.org/",
|
944
|
-
# prefixes: {schema: "http://schema.org/"},
|
945
|
-
# language: "en")
|
946
|
-
#
|
947
|
-
# @param [RDF::Queryable] graph
|
948
|
-
#
|
949
|
-
# @return [self]
|
950
|
-
def from_vocabulary(graph)
|
951
|
-
statements = {}
|
952
|
-
ranges = {}
|
953
|
-
|
954
|
-
# Add term definitions for each class and property not in schema:, and
|
955
|
-
# for those properties having an object range
|
956
|
-
graph.each do |statement|
|
957
|
-
next if statement.subject.node?
|
958
|
-
(statements[statement.subject] ||= []) << statement
|
959
|
-
|
960
|
-
# Keep track of predicate ranges
|
961
|
-
if [RDF::RDFS.range, RDF::SCHEMA.rangeIncludes].include?(statement.predicate)
|
962
|
-
(ranges[statement.subject] ||= []) << statement.object
|
963
|
-
end
|
1135
|
+
term_sym = term.empty? ? "" : term.to_sym
|
1136
|
+
iri_to_term.delete(term_definitions[term].id.to_s) if term_definitions[term].id.is_a?(String)
|
1137
|
+
@options[:prefixes][term_sym] = value if @options.key?(:prefixes)
|
1138
|
+
iri_to_term[value.to_s] = term
|
1139
|
+
term_definitions[term]
|
964
1140
|
end
|
965
1141
|
|
966
|
-
|
967
|
-
#
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
term
|
1142
|
+
##
|
1143
|
+
# Find a term definition
|
1144
|
+
#
|
1145
|
+
# @param [Term, #to_s] term in unexpanded form
|
1146
|
+
# @return [Term]
|
1147
|
+
def find_definition(term)
|
1148
|
+
term.is_a?(TermDefinition) ? term : term_definitions[term.to_s]
|
1149
|
+
end
|
973
1150
|
|
974
|
-
|
975
|
-
|
976
|
-
|
1151
|
+
##
|
1152
|
+
# Retrieve container mapping, add it if `value` is provided
|
1153
|
+
#
|
1154
|
+
# @param [Term, #to_s] term in unexpanded form
|
1155
|
+
# @return [Array<'@index', '@language', '@index', '@set', '@type', '@id', '@graph'>]
|
1156
|
+
def container(term)
|
1157
|
+
return Set[term] if term == '@list'
|
977
1158
|
|
978
|
-
|
979
|
-
|
980
|
-
else
|
981
|
-
prop_ranges = ranges.fetch(subject, [])
|
982
|
-
# If any range is empty or member of range includes rdfs:Literal or schema:Text
|
983
|
-
next if vocab && prop_ranges.empty? ||
|
984
|
-
prop_ranges.include?(RDF::SCHEMA.Text) ||
|
985
|
-
prop_ranges.include?(RDF::RDFS.Literal)
|
986
|
-
td = term_definitions[term] = TermDefinition.new(term, id: subject.to_s)
|
987
|
-
|
988
|
-
# Set context typing based on first element in range
|
989
|
-
case r = prop_ranges.first
|
990
|
-
when RDF::XSD.string
|
991
|
-
if self.default_language
|
992
|
-
td.language_mapping = false
|
993
|
-
end
|
994
|
-
# FIXME: text direction
|
995
|
-
when RDF::XSD.boolean, RDF::SCHEMA.Boolean, RDF::XSD.date, RDF::SCHEMA.Date,
|
996
|
-
RDF::XSD.dateTime, RDF::SCHEMA.DateTime, RDF::XSD.time, RDF::SCHEMA.Time,
|
997
|
-
RDF::XSD.duration, RDF::SCHEMA.Duration, RDF::XSD.decimal, RDF::SCHEMA.Number,
|
998
|
-
RDF::XSD.float, RDF::SCHEMA.Float, RDF::XSD.integer, RDF::SCHEMA.Integer
|
999
|
-
td.type_mapping = r
|
1000
|
-
td.simple = false
|
1001
|
-
else
|
1002
|
-
# It's an object range (includes schema:URL)
|
1003
|
-
td.type_mapping = '@id'
|
1004
|
-
end
|
1005
|
-
end
|
1159
|
+
term = find_definition(term)
|
1160
|
+
term ? term.container_mapping : Set.new
|
1006
1161
|
end
|
1007
1162
|
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
def set_mapping(term, value)
|
1018
|
-
#log_debug("") {"map #{term.inspect} to #{value.inspect}"}
|
1019
|
-
term = term.to_s
|
1020
|
-
term_definitions[term] = TermDefinition.new(term, id: value, simple: true, prefix: (value.to_s.end_with?(*PREFIX_URI_ENDINGS)))
|
1021
|
-
term_definitions[term].simple = true
|
1022
|
-
|
1023
|
-
term_sym = term.empty? ? "" : term.to_sym
|
1024
|
-
iri_to_term.delete(term_definitions[term].id.to_s) if term_definitions[term].id.is_a?(String)
|
1025
|
-
@options[:prefixes][term_sym] = value if @options.key?(:prefixes)
|
1026
|
-
iri_to_term[value.to_s] = term
|
1027
|
-
term_definitions[term]
|
1028
|
-
end
|
1163
|
+
##
|
1164
|
+
# Retrieve term coercion
|
1165
|
+
#
|
1166
|
+
# @param [Term, #to_s] term in unexpanded form
|
1167
|
+
# @return [RDF::URI, '@id']
|
1168
|
+
def coerce(term)
|
1169
|
+
# Map property, if it's not an RDF::Value
|
1170
|
+
# @type is always is an IRI
|
1171
|
+
return '@id' if term == RDF.type || term == '@type'
|
1029
1172
|
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
# @param [Term, #to_s] term in unexpanded form
|
1034
|
-
# @return [Term]
|
1035
|
-
def find_definition(term)
|
1036
|
-
term.is_a?(TermDefinition) ? term : term_definitions[term.to_s]
|
1037
|
-
end
|
1173
|
+
term = find_definition(term)
|
1174
|
+
term&.type_mapping
|
1175
|
+
end
|
1038
1176
|
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
term = find_definition(term)
|
1047
|
-
term ? term.container_mapping : Set.new
|
1048
|
-
end
|
1177
|
+
##
|
1178
|
+
# Should values be represented using an array?
|
1179
|
+
#
|
1180
|
+
# @param [Term, #to_s] term in unexpanded form
|
1181
|
+
# @return [Boolean]
|
1182
|
+
def as_array?(term)
|
1183
|
+
return true if CONTEXT_CONTAINER_ARRAY_TERMS.include?(term)
|
1049
1184
|
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
# @param [Term, #to_s] term in unexpanded form
|
1054
|
-
# @return [RDF::URI, '@id']
|
1055
|
-
def coerce(term)
|
1056
|
-
# Map property, if it's not an RDF::Value
|
1057
|
-
# @type is always is an IRI
|
1058
|
-
return '@id' if term == RDF.type || term == '@type'
|
1059
|
-
term = find_definition(term)
|
1060
|
-
term && term.type_mapping
|
1061
|
-
end
|
1185
|
+
term = find_definition(term)
|
1186
|
+
term && (term.as_set? || term.container_mapping.include?('@list'))
|
1187
|
+
end
|
1062
1188
|
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
end
|
1189
|
+
##
|
1190
|
+
# Retrieve content of a term
|
1191
|
+
#
|
1192
|
+
# @param [Term, #to_s] term in unexpanded form
|
1193
|
+
# @return [Hash]
|
1194
|
+
def content(term)
|
1195
|
+
term = find_definition(term)
|
1196
|
+
term&.content
|
1197
|
+
end
|
1073
1198
|
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
term
|
1081
|
-
|
1082
|
-
|
1199
|
+
##
|
1200
|
+
# Retrieve nest of a term.
|
1201
|
+
# value of nest must be @nest or a term that resolves to @nest
|
1202
|
+
#
|
1203
|
+
# @param [Term, #to_s] term in unexpanded form
|
1204
|
+
# @return [String] Nesting term
|
1205
|
+
# @raise JsonLdError::InvalidNestValue if nesting term exists and is not a term resolving to `@nest` in the current context.
|
1206
|
+
def nest(term)
|
1207
|
+
term = find_definition(term)
|
1208
|
+
return unless term
|
1083
1209
|
|
1084
|
-
##
|
1085
|
-
# Retrieve nest of a term.
|
1086
|
-
# value of nest must be @nest or a term that resolves to @nest
|
1087
|
-
#
|
1088
|
-
# @param [Term, #to_s] term in unexpanded form
|
1089
|
-
# @return [String] Nesting term
|
1090
|
-
# @raise JsonLdError::InvalidNestValue if nesting term exists and is not a term resolving to `@nest` in the current context.
|
1091
|
-
def nest(term)
|
1092
|
-
term = find_definition(term)
|
1093
|
-
if term
|
1094
1210
|
case term.nest
|
1095
1211
|
when '@nest', nil
|
1096
|
-
term.nest
|
1097
1212
|
else
|
1098
1213
|
nest_term = find_definition(term.nest)
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
end
|
1104
|
-
|
1105
|
-
##
|
1106
|
-
# Retrieve the language associated with a term, or the default language otherwise
|
1107
|
-
# @param [Term, #to_s] term in unexpanded form
|
1108
|
-
# @return [String]
|
1109
|
-
def language(term)
|
1110
|
-
term = find_definition(term)
|
1111
|
-
lang = term && term.language_mapping
|
1112
|
-
lang.nil? ? @default_language : (lang == false ? nil : lang)
|
1113
|
-
end
|
1114
|
-
|
1115
|
-
##
|
1116
|
-
# Retrieve the text direction associated with a term, or the default direction otherwise
|
1117
|
-
# @param [Term, #to_s] term in unexpanded form
|
1118
|
-
# @return [String]
|
1119
|
-
def direction(term)
|
1120
|
-
term = find_definition(term)
|
1121
|
-
dir = term && term.direction_mapping
|
1122
|
-
dir.nil? ? @default_direction : (dir == false ? nil : dir)
|
1123
|
-
end
|
1124
|
-
|
1125
|
-
##
|
1126
|
-
# Is this a reverse term
|
1127
|
-
# @param [Term, #to_s] term in unexpanded form
|
1128
|
-
# @return [Boolean]
|
1129
|
-
def reverse?(term)
|
1130
|
-
term = find_definition(term)
|
1131
|
-
term && term.reverse_property
|
1132
|
-
end
|
1214
|
+
unless nest_term && nest_term.id == '@nest'
|
1215
|
+
raise JsonLdError::InvalidNestValue,
|
1216
|
+
"nest must a term resolving to @nest, was #{nest_term.inspect}"
|
1217
|
+
end
|
1133
1218
|
|
1134
|
-
|
1135
|
-
|
1136
|
-
#
|
1137
|
-
# @param [Term, #to_s] term
|
1138
|
-
# @return [Term] related term definition
|
1139
|
-
def reverse_term(term)
|
1140
|
-
# Direct lookup of term
|
1141
|
-
term = term_definitions[term.to_s] if term_definitions.key?(term.to_s) && !term.is_a?(TermDefinition)
|
1142
|
-
|
1143
|
-
# Lookup term, assuming term is an IRI
|
1144
|
-
unless term.is_a?(TermDefinition)
|
1145
|
-
td = term_definitions.values.detect {|t| t.id == term.to_s}
|
1146
|
-
|
1147
|
-
# Otherwise create a temporary term definition
|
1148
|
-
term = td || TermDefinition.new(term.to_s, id: expand_iri(term, vocab:true))
|
1219
|
+
end
|
1220
|
+
term.nest
|
1149
1221
|
end
|
1150
1222
|
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
# Used during Context Processing.
|
1164
|
-
# @param [Boolean] documentRelative (false)
|
1165
|
-
# @param [Hash] local_context
|
1166
|
-
# Used during Context Processing.
|
1167
|
-
# @param [Boolean] vocab (false)
|
1168
|
-
# @param [Hash{Symbol => Object}] options
|
1169
|
-
# @return [RDF::Resource, String]
|
1170
|
-
# IRI or String, if it's a keyword
|
1171
|
-
# @raise [JSON::LD::JsonLdError::InvalidIRIMapping] if the value cannot be expanded
|
1172
|
-
# @see https://www.w3.org/TR/json-ld11-api/#iri-expansion
|
1173
|
-
def expand_iri(value,
|
1174
|
-
as_string: false,
|
1175
|
-
base: nil,
|
1176
|
-
defined: nil,
|
1177
|
-
documentRelative: false,
|
1178
|
-
local_context: nil,
|
1179
|
-
vocab: false,
|
1180
|
-
**options)
|
1181
|
-
return (value && as_string ? value.to_s : value) unless value.is_a?(String)
|
1182
|
-
|
1183
|
-
return value if KEYWORDS.include?(value)
|
1184
|
-
return nil if value.match?(/^@[a-zA-Z]+$/)
|
1185
|
-
|
1186
|
-
defined = defined || {} # if we initialized in the keyword arg we would allocate {} at each invokation, even in the 2 (common) early returns above.
|
1187
|
-
|
1188
|
-
# 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.
|
1189
|
-
if local_context && local_context.key?(value) && !defined[value]
|
1190
|
-
create_term_definition(local_context, value, defined)
|
1223
|
+
##
|
1224
|
+
# Retrieve the language associated with a term, or the default language otherwise
|
1225
|
+
# @param [Term, #to_s] term in unexpanded form
|
1226
|
+
# @return [String]
|
1227
|
+
def language(term)
|
1228
|
+
term = find_definition(term)
|
1229
|
+
lang = term&.language_mapping
|
1230
|
+
if lang.nil?
|
1231
|
+
@default_language
|
1232
|
+
else
|
1233
|
+
(lang == false ? nil : lang)
|
1234
|
+
end
|
1191
1235
|
end
|
1192
1236
|
|
1193
|
-
|
1194
|
-
|
1237
|
+
##
|
1238
|
+
# Retrieve the text direction associated with a term, or the default direction otherwise
|
1239
|
+
# @param [Term, #to_s] term in unexpanded form
|
1240
|
+
# @return [String]
|
1241
|
+
def direction(term)
|
1242
|
+
term = find_definition(term)
|
1243
|
+
dir = term&.direction_mapping
|
1244
|
+
if dir.nil?
|
1245
|
+
@default_direction
|
1246
|
+
else
|
1247
|
+
(dir == false ? nil : dir)
|
1248
|
+
end
|
1195
1249
|
end
|
1196
1250
|
|
1197
|
-
|
1198
|
-
#
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1251
|
+
##
|
1252
|
+
# Is this a reverse term
|
1253
|
+
# @param [Term, #to_s] term in unexpanded form
|
1254
|
+
# @return [Boolean]
|
1255
|
+
def reverse?(term)
|
1256
|
+
term = find_definition(term)
|
1257
|
+
term&.reverse_property
|
1202
1258
|
end
|
1203
1259
|
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1260
|
+
##
|
1261
|
+
# Given a term or IRI, find a reverse term definition matching that term. If the term is already reversed, find a non-reversed version.
|
1262
|
+
#
|
1263
|
+
# @param [Term, #to_s] term
|
1264
|
+
# @return [Term] related term definition
|
1265
|
+
def reverse_term(term)
|
1266
|
+
# Direct lookup of term
|
1267
|
+
term = term_definitions[term.to_s] if term_definitions.key?(term.to_s) && !term.is_a?(TermDefinition)
|
1268
|
+
|
1269
|
+
# Lookup term, assuming term is an IRI
|
1270
|
+
unless term.is_a?(TermDefinition)
|
1271
|
+
td = term_definitions.values.detect { |t| t.id == term.to_s }
|
1272
|
+
|
1273
|
+
# Otherwise create a temporary term definition
|
1274
|
+
term = td || TermDefinition.new(term.to_s, id: expand_iri(term, vocab: true))
|
1212
1275
|
end
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1276
|
+
|
1277
|
+
# Now, return a term, which reverses this term
|
1278
|
+
term_definitions.values.detect { |t| t.id == term.id && t.reverse_property != term.reverse_property }
|
1279
|
+
end
|
1280
|
+
|
1281
|
+
##
|
1282
|
+
# Expand an IRI. Relative IRIs are expanded against any document base.
|
1283
|
+
#
|
1284
|
+
# @param [String] value
|
1285
|
+
# A keyword, term, prefix:suffix or possibly relative IRI
|
1286
|
+
# @param [Boolean] as_string (false) transform RDF::Resource values to string
|
1287
|
+
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1288
|
+
# @param [Hash] defined
|
1289
|
+
# Used during Context Processing.
|
1290
|
+
# @param [Boolean] documentRelative (false)
|
1291
|
+
# @param [Hash] local_context
|
1292
|
+
# Used during Context Processing.
|
1293
|
+
# @param [Boolean] vocab (false)
|
1294
|
+
# @param [Hash{Symbol => Object}] options
|
1295
|
+
# @return [RDF::Resource, String]
|
1296
|
+
# IRI or String, if it's a keyword
|
1297
|
+
# @raise [JSON::LD::JsonLdError::InvalidIRIMapping] if the value cannot be expanded
|
1298
|
+
# @see https://www.w3.org/TR/json-ld11-api/#iri-expansion
|
1299
|
+
def expand_iri(value,
|
1300
|
+
as_string: false,
|
1301
|
+
base: nil,
|
1302
|
+
defined: nil,
|
1303
|
+
documentRelative: false,
|
1304
|
+
local_context: nil,
|
1305
|
+
vocab: false,
|
1306
|
+
**_options)
|
1307
|
+
return (value && as_string ? value.to_s : value) unless value.is_a?(String)
|
1308
|
+
|
1309
|
+
return value if KEYWORDS.include?(value)
|
1310
|
+
return nil if value.match?(/^@[a-zA-Z]+$/)
|
1311
|
+
|
1312
|
+
defined ||= {} # if we initialized in the keyword arg we would allocate {} at each invokation, even in the 2 (common) early returns above.
|
1313
|
+
|
1314
|
+
# 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.
|
1315
|
+
create_term_definition(local_context, value, defined) if local_context&.key?(value) && !defined[value]
|
1316
|
+
|
1317
|
+
if (v_td = term_definitions[value]) && KEYWORDS.include?(v_td.id)
|
1318
|
+
return (as_string ? v_td.id.to_s : v_td.id)
|
1216
1319
|
end
|
1217
1320
|
|
1218
|
-
# If
|
1219
|
-
|
1220
|
-
|
1321
|
+
# If active context has a term definition for value, and the associated mapping is a keyword, return that keyword.
|
1322
|
+
# If vocab is true and the active context has a term definition for value, return the associated IRI mapping.
|
1323
|
+
if (v_td = term_definitions[value]) && (vocab || KEYWORDS.include?(v_td.id))
|
1324
|
+
iri = base && v_td.id ? base.join(v_td.id) : v_td.id # vocab might be doc relative
|
1325
|
+
return (as_string ? iri.to_s : iri)
|
1221
1326
|
end
|
1222
1327
|
|
1223
|
-
# If
|
1224
|
-
if
|
1225
|
-
|
1226
|
-
|
1227
|
-
#
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1328
|
+
# If value contains a colon (:), it is either an absolute IRI or a compact IRI:
|
1329
|
+
if value[1..].to_s.include?(':')
|
1330
|
+
prefix, suffix = value.split(':', 2)
|
1331
|
+
|
1332
|
+
# If prefix is underscore (_) or suffix begins with double-forward-slash (//), return value as it is already an absolute IRI or a blank node identifier.
|
1333
|
+
if prefix == '_'
|
1334
|
+
v = RDF::Node.new(namer.get_sym(suffix))
|
1335
|
+
return (as_string ? v.to_s : v)
|
1336
|
+
end
|
1337
|
+
if suffix.start_with?('//')
|
1338
|
+
v = RDF::URI(value)
|
1339
|
+
return (as_string ? v.to_s : v)
|
1340
|
+
end
|
1341
|
+
|
1342
|
+
# 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.
|
1343
|
+
create_term_definition(local_context, prefix, defined) if local_context&.key?(prefix) && !defined[prefix]
|
1344
|
+
|
1345
|
+
# If active context contains a term definition for prefix, return the result of concatenating the IRI mapping associated with prefix and suffix.
|
1346
|
+
if (td = term_definitions[prefix]) && !td.id.nil? && td.prefix?
|
1347
|
+
return (as_string ? td.id.to_s : td.id) + suffix
|
1348
|
+
elsif RDF::URI(value).absolute?
|
1349
|
+
# Otherwise, if the value has the form of an absolute IRI, return it
|
1350
|
+
return (as_string ? value.to_s : RDF::URI(value))
|
1351
|
+
end
|
1231
1352
|
end
|
1232
|
-
end
|
1233
1353
|
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1354
|
+
iri = value.is_a?(RDF::URI) ? value : RDF::URI(value)
|
1355
|
+
result = if vocab && self.vocab
|
1356
|
+
# If vocab is true, and active context has a vocabulary mapping, return the result of concatenating the vocabulary mapping with value.
|
1357
|
+
# Note that @vocab could still be relative to a document base
|
1358
|
+
(base && self.vocab.is_a?(RDF::URI) && self.vocab.relative? ? base.join(self.vocab) : self.vocab) + value
|
1359
|
+
elsif documentRelative
|
1360
|
+
if iri.absolute?
|
1361
|
+
iri
|
1362
|
+
elsif self.base.is_a?(RDF::URI) && self.base.absolute?
|
1363
|
+
self.base.join(iri)
|
1364
|
+
elsif self.base == false
|
1365
|
+
# No resollution of `@base: null`
|
1366
|
+
iri
|
1367
|
+
elsif base && self.base
|
1368
|
+
base.join(self.base).join(iri)
|
1369
|
+
elsif base
|
1370
|
+
base.join(iri)
|
1371
|
+
else
|
1372
|
+
# Returns a relative IRI in an odd case.
|
1373
|
+
iri
|
1374
|
+
end
|
1375
|
+
elsif local_context && iri.relative?
|
1376
|
+
# 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.
|
1377
|
+
raise JSON::LD::JsonLdError::InvalidIRIMapping, "not an absolute IRI: #{value}"
|
1251
1378
|
else
|
1252
|
-
# Returns a relative IRI in an odd case.
|
1253
1379
|
iri
|
1254
1380
|
end
|
1255
|
-
|
1256
|
-
# 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.
|
1257
|
-
raise JSON::LD::JsonLdError::InvalidIRIMapping, "not an absolute IRI: #{value}"
|
1258
|
-
else
|
1259
|
-
iri
|
1381
|
+
result && as_string ? result.to_s : result
|
1260
1382
|
end
|
1261
|
-
result && as_string ? result.to_s : result
|
1262
|
-
end
|
1263
1383
|
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1284
|
-
|
1285
|
-
|
1286
|
-
|
1287
|
-
|
1288
|
-
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1292
|
-
|
1293
|
-
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
1303
|
-
|
1304
|
-
|
1305
|
-
|
1306
|
-
|
1307
|
-
|
1308
|
-
|
1309
|
-
|
1310
|
-
|
1311
|
-
|
1312
|
-
|
1313
|
-
list
|
1314
|
-
|
1315
|
-
|
1316
|
-
|
1317
|
-
|
1318
|
-
if
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1384
|
+
# The following constants are used to reduce object allocations in #compact_iri below
|
1385
|
+
CONTAINERS_GRAPH = %w[@graph@id @graph@id@set].freeze
|
1386
|
+
CONTAINERS_GRAPH_INDEX = %w[@graph@index @graph@index@set].freeze
|
1387
|
+
CONTAINERS_GRAPH_INDEX_INDEX = %w[@graph@index @graph@index@set @index @index@set].freeze
|
1388
|
+
CONTAINERS_GRAPH_SET = %w[@graph @graph@set @set].freeze
|
1389
|
+
CONTAINERS_ID_TYPE = %w[@id @id@set @type @set@type].freeze
|
1390
|
+
CONTAINERS_ID_VOCAB = %w[@id @vocab @none].freeze
|
1391
|
+
CONTAINERS_INDEX_SET = %w[@index @index@set].freeze
|
1392
|
+
CONTAINERS_LANGUAGE = %w[@language @language@set].freeze
|
1393
|
+
CONTAINERS_VALUE = %w[@value].freeze
|
1394
|
+
CONTAINERS_VOCAB_ID = %w[@vocab @id @none].freeze
|
1395
|
+
|
1396
|
+
##
|
1397
|
+
# Compacts an absolute IRI to the shortest matching term or compact IRI
|
1398
|
+
#
|
1399
|
+
# @param [RDF::URI] iri
|
1400
|
+
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1401
|
+
# @param [Object] value
|
1402
|
+
# Value, used to select among various maps for the same IRI
|
1403
|
+
# @param [Boolean] reverse
|
1404
|
+
# specifies whether a reverse property is being compacted
|
1405
|
+
# @param [Boolean] vocab
|
1406
|
+
# specifies whether the passed iri should be compacted using the active context's vocabulary mapping
|
1407
|
+
#
|
1408
|
+
# @return [String] compacted form of IRI
|
1409
|
+
# @see https://www.w3.org/TR/json-ld11-api/#iri-compaction
|
1410
|
+
def compact_iri(iri, base: nil, reverse: false, value: nil, vocab: nil)
|
1411
|
+
return if iri.nil?
|
1412
|
+
|
1413
|
+
iri = iri.to_s
|
1414
|
+
|
1415
|
+
if vocab && inverse_context.key?(iri)
|
1416
|
+
default_language = if default_direction
|
1417
|
+
"#{self.default_language}_#{default_direction}".downcase
|
1418
|
+
else
|
1419
|
+
(self.default_language || "@none").downcase
|
1420
|
+
end
|
1421
|
+
containers = []
|
1422
|
+
tl = "@language"
|
1423
|
+
tl_value = "@null"
|
1424
|
+
containers.concat(CONTAINERS_INDEX_SET) if index?(value) && !graph?(value)
|
1425
|
+
|
1426
|
+
# If the value is a JSON Object with the key @preserve, use the value of @preserve.
|
1427
|
+
value = value['@preserve'].first if value.is_a?(Hash) && value.key?('@preserve')
|
1428
|
+
|
1429
|
+
if reverse
|
1430
|
+
tl = "@type"
|
1431
|
+
tl_value = "@reverse"
|
1432
|
+
containers << '@set'
|
1433
|
+
elsif list?(value)
|
1434
|
+
# if value is a list object, then set type/language and type/language value to the most specific values that work for all items in the list as follows:
|
1435
|
+
containers << "@list" unless index?(value)
|
1436
|
+
list = value['@list']
|
1437
|
+
common_type = nil
|
1438
|
+
common_language = default_language if list.empty?
|
1439
|
+
list.each do |item|
|
1440
|
+
item_language = "@none"
|
1441
|
+
item_type = "@none"
|
1442
|
+
if value?(item)
|
1443
|
+
if item.key?('@direction')
|
1444
|
+
item_language = "#{item['@language']}_#{item['@direction']}".downcase
|
1445
|
+
elsif item.key?('@language')
|
1446
|
+
item_language = item['@language'].downcase
|
1447
|
+
elsif item.key?('@type')
|
1448
|
+
item_type = item['@type']
|
1449
|
+
else
|
1450
|
+
item_language = "@null"
|
1451
|
+
end
|
1325
1452
|
else
|
1326
|
-
|
1453
|
+
item_type = '@id'
|
1327
1454
|
end
|
1328
|
-
|
1329
|
-
|
1330
|
-
|
1331
|
-
|
1332
|
-
if item_language != common_language && value?(item)
|
1333
|
-
common_language = '@none'
|
1334
|
-
end
|
1335
|
-
common_type ||= item_type
|
1336
|
-
if item_type != common_type
|
1337
|
-
common_type = '@none'
|
1455
|
+
common_language ||= item_language
|
1456
|
+
common_language = '@none' if item_language != common_language && value?(item)
|
1457
|
+
common_type ||= item_type
|
1458
|
+
common_type = '@none' if item_type != common_type
|
1338
1459
|
end
|
1339
|
-
end
|
1340
1460
|
|
1341
|
-
|
1342
|
-
|
1343
|
-
|
1344
|
-
|
1461
|
+
common_language ||= '@none'
|
1462
|
+
common_type ||= '@none'
|
1463
|
+
if common_type == '@none'
|
1464
|
+
tl_value = common_language
|
1465
|
+
else
|
1466
|
+
tl = '@type'
|
1467
|
+
tl_value = common_type
|
1468
|
+
end
|
1469
|
+
elsif graph?(value)
|
1470
|
+
# Prefer @index and @id containers, then @graph, then @index
|
1471
|
+
containers.concat(CONTAINERS_GRAPH_INDEX_INDEX) if index?(value)
|
1472
|
+
containers.concat(CONTAINERS_GRAPH) if value.key?('@id')
|
1473
|
+
|
1474
|
+
# Prefer an @graph container next
|
1475
|
+
containers.concat(CONTAINERS_GRAPH_SET)
|
1476
|
+
|
1477
|
+
# Lastly, in 1.1, any graph can be indexed on @index or @id, so add if we haven't already
|
1478
|
+
containers.concat(CONTAINERS_GRAPH_INDEX) unless index?(value)
|
1479
|
+
containers.concat(CONTAINERS_GRAPH) unless value.key?('@id')
|
1480
|
+
containers.concat(CONTAINERS_INDEX_SET) unless index?(value)
|
1481
|
+
containers << '@set'
|
1482
|
+
|
1483
|
+
tl = '@type'
|
1484
|
+
tl_value = '@id'
|
1345
1485
|
else
|
1346
|
-
|
1486
|
+
if value?(value)
|
1487
|
+
# In 1.1, an language map can be used to index values using @none
|
1488
|
+
if value.key?('@language') && !index?(value)
|
1489
|
+
tl_value = value['@language'].downcase
|
1490
|
+
tl_value += "_#{value['@direction']}" if value['@direction']
|
1491
|
+
containers.concat(CONTAINERS_LANGUAGE)
|
1492
|
+
elsif value.key?('@direction') && !index?(value)
|
1493
|
+
tl_value = "_#{value['@direction']}"
|
1494
|
+
elsif value.key?('@type')
|
1495
|
+
tl_value = value['@type']
|
1496
|
+
tl = '@type'
|
1497
|
+
end
|
1498
|
+
else
|
1499
|
+
# In 1.1, an id or type map can be used to index values using @none
|
1500
|
+
containers.concat(CONTAINERS_ID_TYPE)
|
1501
|
+
tl = '@type'
|
1502
|
+
tl_value = '@id'
|
1503
|
+
end
|
1504
|
+
containers << '@set'
|
1347
1505
|
end
|
1348
|
-
elsif graph?(value)
|
1349
|
-
# Prefer @index and @id containers, then @graph, then @index
|
1350
|
-
containers.concat(CONTAINERS_GRAPH_INDEX_INDEX) if index?(value)
|
1351
|
-
containers.concat(CONTAINERS_GRAPH) if value.key?('@id')
|
1352
1506
|
|
1353
|
-
|
1354
|
-
containers.concat(CONTAINERS_GRAPH_SET)
|
1507
|
+
containers << '@none'
|
1355
1508
|
|
1356
|
-
#
|
1357
|
-
containers.concat(CONTAINERS_GRAPH_INDEX) unless index?(value)
|
1358
|
-
containers.concat(CONTAINERS_GRAPH) unless value.key?('@id')
|
1509
|
+
# In 1.1, an index map can be used to index values using @none, so add as a low priority
|
1359
1510
|
containers.concat(CONTAINERS_INDEX_SET) unless index?(value)
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
elsif value.key?('@type')
|
1373
|
-
tl_value = value['@type']
|
1374
|
-
tl = '@type'
|
1511
|
+
# Values without type or language can use @language map
|
1512
|
+
containers.concat(CONTAINERS_LANGUAGE) if value?(value) && value.keys == CONTAINERS_VALUE
|
1513
|
+
|
1514
|
+
tl_value ||= '@null'
|
1515
|
+
preferred_values = []
|
1516
|
+
preferred_values << '@reverse' if tl_value == '@reverse'
|
1517
|
+
if ['@id', '@reverse'].include?(tl_value) && value.is_a?(Hash) && value.key?('@id')
|
1518
|
+
t_iri = compact_iri(value['@id'], vocab: true, base: base)
|
1519
|
+
if (r_td = term_definitions[t_iri]) && r_td.id == value['@id']
|
1520
|
+
preferred_values.concat(CONTAINERS_VOCAB_ID)
|
1521
|
+
else
|
1522
|
+
preferred_values.concat(CONTAINERS_ID_VOCAB)
|
1375
1523
|
end
|
1376
1524
|
else
|
1377
|
-
|
1378
|
-
|
1379
|
-
tl, tl_value = '@type', '@id'
|
1525
|
+
tl = '@any' if list?(value) && value['@list'].empty?
|
1526
|
+
preferred_values.concat([tl_value, '@none'].compact)
|
1380
1527
|
end
|
1381
|
-
|
1382
|
-
end
|
1383
|
-
|
1384
|
-
containers << '@none'
|
1528
|
+
preferred_values << '@any'
|
1385
1529
|
|
1386
|
-
|
1387
|
-
|
1388
|
-
|
1389
|
-
containers.concat(CONTAINERS_LANGUAGE) if value?(value) && value.keys == CONTAINERS_VALUE
|
1390
|
-
|
1391
|
-
tl_value ||= '@null'
|
1392
|
-
preferred_values = []
|
1393
|
-
preferred_values << '@reverse' if tl_value == '@reverse'
|
1394
|
-
if (tl_value == '@id' || tl_value == '@reverse') && value.is_a?(Hash) && value.key?('@id')
|
1395
|
-
t_iri = compact_iri(value['@id'], vocab: true, base: base)
|
1396
|
-
if (r_td = term_definitions[t_iri]) && r_td.id == value['@id']
|
1397
|
-
preferred_values.concat(CONTAINERS_VOCAB_ID)
|
1398
|
-
else
|
1399
|
-
preferred_values.concat(CONTAINERS_ID_VOCAB)
|
1530
|
+
# if containers included `@language` and preferred_values includes something of the form language-tag_direction, add just the _direction part, to select terms that have that direction.
|
1531
|
+
if (lang_dir = preferred_values.detect { |v| v.include?('_') })
|
1532
|
+
preferred_values << ('_' + lang_dir.split('_').last)
|
1400
1533
|
end
|
1401
|
-
else
|
1402
|
-
tl = '@any' if list?(value) && value['@list'].empty?
|
1403
|
-
preferred_values.concat([tl_value, '@none'].compact)
|
1404
|
-
end
|
1405
|
-
preferred_values << '@any'
|
1406
1534
|
|
1407
|
-
|
1408
|
-
|
1409
|
-
|
1535
|
+
if (p_term = select_term(iri, containers, tl, preferred_values))
|
1536
|
+
return p_term
|
1537
|
+
end
|
1410
1538
|
end
|
1411
1539
|
|
1412
|
-
|
1413
|
-
|
1540
|
+
# 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:
|
1541
|
+
if vocab && self.vocab && iri.start_with?(self.vocab) && iri.length > self.vocab.length
|
1542
|
+
suffix = iri[self.vocab.length..]
|
1543
|
+
return suffix unless term_definitions.key?(suffix)
|
1414
1544
|
end
|
1415
|
-
end
|
1416
1545
|
|
1417
|
-
|
1418
|
-
|
1419
|
-
suffix = iri[self.vocab.length..-1]
|
1420
|
-
return suffix unless term_definitions.key?(suffix)
|
1421
|
-
end
|
1546
|
+
# 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.
|
1547
|
+
candidates = []
|
1422
1548
|
|
1423
|
-
|
1424
|
-
|
1549
|
+
term_definitions.each do |term, td|
|
1550
|
+
# Skip term if `@prefix` is not true in term definition
|
1551
|
+
next unless td&.prefix?
|
1425
1552
|
|
1426
|
-
|
1427
|
-
next if td.nil? || td.id.nil? || td.id == iri || !iri.start_with?(td.id)
|
1553
|
+
next if td&.id.nil? || td.id == iri || !td.match_iri?(iri)
|
1428
1554
|
|
1429
|
-
|
1430
|
-
|
1555
|
+
suffix = iri[td.id.length..]
|
1556
|
+
ciri = "#{term}:#{suffix}"
|
1557
|
+
candidates << ciri unless value && term_definitions.key?(ciri)
|
1558
|
+
end
|
1431
1559
|
|
1432
|
-
|
1433
|
-
|
1434
|
-
|
1435
|
-
|
1560
|
+
return candidates.min unless candidates.empty?
|
1561
|
+
|
1562
|
+
# If we still don't have any terms and we're using standard_prefixes,
|
1563
|
+
# try those, and add to mapping
|
1564
|
+
if @options[:standard_prefixes]
|
1565
|
+
candidates = RDF::Vocabulary
|
1566
|
+
.select { |v| iri.start_with?(v.to_uri.to_s) && iri != v.to_uri.to_s }
|
1567
|
+
.map do |v|
|
1568
|
+
prefix = v.__name__.to_s.split('::').last.downcase
|
1569
|
+
set_mapping(prefix, v.to_uri.to_s)
|
1570
|
+
iri.sub(v.to_uri.to_s, "#{prefix}:").sub(/:$/, '')
|
1571
|
+
end
|
1436
1572
|
|
1437
|
-
|
1573
|
+
return candidates.min unless candidates.empty?
|
1574
|
+
end
|
1438
1575
|
|
1439
|
-
|
1440
|
-
|
1441
|
-
|
1442
|
-
candidates = RDF::Vocabulary.
|
1443
|
-
select {|v| iri.start_with?(v.to_uri.to_s) && iri != v.to_uri.to_s}.
|
1444
|
-
map do |v|
|
1445
|
-
prefix = v.__name__.to_s.split('::').last.downcase
|
1446
|
-
set_mapping(prefix, v.to_uri.to_s)
|
1447
|
-
iri.sub(v.to_uri.to_s, "#{prefix}:").sub(/:$/, '')
|
1448
|
-
end
|
1576
|
+
# If iri could be confused with a compact IRI using a term in this context, signal an error
|
1577
|
+
term_definitions.each do |term, td|
|
1578
|
+
next unless td.prefix? && td.match_compact_iri?(iri)
|
1449
1579
|
|
1450
|
-
|
1451
|
-
|
1580
|
+
raise JSON::LD::JsonLdError::IRIConfusedWithPrefix, "Absolute IRI '#{iri}' confused with prefix '#{term}'"
|
1581
|
+
end
|
1452
1582
|
|
1453
|
-
|
1454
|
-
term_definitions.each do |term, td|
|
1455
|
-
next unless iri.to_s.start_with?("#{term}:") && td.prefix?
|
1456
|
-
raise JSON::LD::JsonLdError:: IRIConfusedWithPrefix, "Absolute IRI '#{iri}' confused with prefix '#{term}'"
|
1457
|
-
end
|
1583
|
+
return iri if vocab
|
1458
1584
|
|
1459
|
-
if !vocab
|
1460
1585
|
# transform iri to a relative IRI using the document's base IRI
|
1461
1586
|
iri = remove_base(self.base || base, iri)
|
1462
1587
|
# Make . relative if it has the form of a keyword.
|
1463
1588
|
iri = "./#{iri}" if iri.match?(/^@[a-zA-Z]+$/)
|
1464
|
-
return iri
|
1465
|
-
else
|
1466
|
-
return iri
|
1467
|
-
end
|
1468
|
-
end
|
1469
1589
|
|
1470
|
-
|
1471
|
-
|
1472
|
-
##
|
1473
|
-
# If active property has a type mapping in the active context set to @id or @vocab, a JSON object with a single member @id whose value is the result of using the IRI Expansion algorithm on value is returned.
|
1474
|
-
#
|
1475
|
-
# Otherwise, the result will be a JSON object containing an @value member whose value is the passed value. Additionally, an @type member will be included if there is a type mapping associated with the active property or an @language member if value is a string and there is language mapping associated with the active property.
|
1476
|
-
#
|
1477
|
-
# @param [String] property
|
1478
|
-
# Associated property used to find coercion rules
|
1479
|
-
# @param [Hash, String] value
|
1480
|
-
# Value (literal or IRI) to be expanded
|
1481
|
-
# @param [Boolean] useNativeTypes (false) use native representations
|
1482
|
-
# @param [Boolean] rdfDirection (nil) decode i18n datatype if i18n-datatype
|
1483
|
-
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1484
|
-
# @param [Hash{Symbol => Object}] options
|
1485
|
-
#
|
1486
|
-
# @return [Hash] Object representation of value
|
1487
|
-
# @raise [RDF::ReaderError] if the iri cannot be expanded
|
1488
|
-
# @see https://www.w3.org/TR/json-ld11-api/#value-expansion
|
1489
|
-
def expand_value(property, value, useNativeTypes: false, rdfDirection: nil, base: nil, **options)
|
1490
|
-
td = term_definitions.fetch(property, TermDefinition.new(property))
|
1491
|
-
|
1492
|
-
# 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.
|
1493
|
-
if value.is_a?(String) && td.type_mapping == '@id'
|
1494
|
-
#log_debug("") {"as relative IRI: #{value.inspect}"}
|
1495
|
-
return {'@id' => expand_iri(value, documentRelative: true, base: base).to_s}
|
1590
|
+
iri
|
1496
1591
|
end
|
1497
1592
|
|
1498
|
-
|
1499
|
-
|
1500
|
-
|
1501
|
-
|
1593
|
+
##
|
1594
|
+
# If active property has a type mapping in the active context set to @id or @vocab, a JSON object with a single member @id whose value is the result of using the IRI Expansion algorithm on value is returned.
|
1595
|
+
#
|
1596
|
+
# Otherwise, the result will be a JSON object containing an @value member whose value is the passed value. Additionally, an @type member will be included if there is a type mapping associated with the active property or an @language member if value is a string and there is language mapping associated with the active property.
|
1597
|
+
#
|
1598
|
+
# @param [String] property
|
1599
|
+
# Associated property used to find coercion rules
|
1600
|
+
# @param [Hash, String] value
|
1601
|
+
# Value (literal or IRI) to be expanded
|
1602
|
+
# @param [Boolean] useNativeTypes (false) use native representations
|
1603
|
+
# @param [Boolean] rdfDirection (nil) decode i18n datatype if i18n-datatype
|
1604
|
+
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1605
|
+
# @param [Hash{Symbol => Object}] options
|
1606
|
+
#
|
1607
|
+
# @return [Hash] Object representation of value
|
1608
|
+
# @raise [RDF::ReaderError] if the iri cannot be expanded
|
1609
|
+
# @see https://www.w3.org/TR/json-ld11-api/#value-expansion
|
1610
|
+
def expand_value(property, value, useNativeTypes: false, rdfDirection: nil, base: nil, **_options)
|
1611
|
+
td = term_definitions.fetch(property, TermDefinition.new(property))
|
1612
|
+
|
1613
|
+
# 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.
|
1614
|
+
if value.is_a?(String) && td.type_mapping == '@id'
|
1615
|
+
# log_debug("") {"as relative IRI: #{value.inspect}"}
|
1616
|
+
return { '@id' => expand_iri(value, documentRelative: true, base: base).to_s }
|
1617
|
+
end
|
1502
1618
|
|
1503
|
-
|
1504
|
-
value.is_a?(
|
1505
|
-
|
1506
|
-
|
1507
|
-
|
1508
|
-
result = case value
|
1509
|
-
when RDF::URI, RDF::Node
|
1510
|
-
{'@id' => value.to_s}
|
1511
|
-
when RDF::Literal
|
1512
|
-
res = {}
|
1513
|
-
if value.datatype == RDF::URI(RDF.to_uri + "JSON") && processingMode('json-ld-1.1')
|
1514
|
-
# Value parsed as JSON
|
1515
|
-
# FIXME: MultiJson
|
1516
|
-
res['@type'] = '@json'
|
1517
|
-
res['@value'] = ::JSON.parse(value.object)
|
1518
|
-
elsif value.datatype.start_with?("https://www.w3.org/ns/i18n#") && rdfDirection == 'i18n-datatype' && processingMode('json-ld-1.1')
|
1519
|
-
lang, dir = value.datatype.fragment.split('_')
|
1520
|
-
res['@value'] = value.to_s
|
1521
|
-
unless lang.empty?
|
1522
|
-
if lang !~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/
|
1523
|
-
if options[:validate]
|
1524
|
-
raise JsonLdError::InvalidLanguageMapping, "rdf:language must be valid BCP47: #{lang.inspect}"
|
1525
|
-
else
|
1526
|
-
warn "rdf:language must be valid BCP47: #{lang.inspect}"
|
1527
|
-
end
|
1528
|
-
end
|
1529
|
-
res['@language'] = lang
|
1530
|
-
end
|
1531
|
-
res['@direction'] = dir
|
1532
|
-
elsif useNativeTypes && RDF_LITERAL_NATIVE_TYPES.include?(value.datatype) && value.valid?
|
1533
|
-
res['@type'] = uri(coerce(property)) if coerce(property)
|
1534
|
-
res['@value'] = value.object
|
1535
|
-
else
|
1536
|
-
value.canonicalize! if value.valid? && value.datatype == RDF::XSD.double
|
1537
|
-
if coerce(property)
|
1538
|
-
res['@type'] = uri(coerce(property)).to_s
|
1539
|
-
elsif value.datatype?
|
1540
|
-
res['@type'] = uri(value.datatype).to_s
|
1541
|
-
elsif value.language? || language(property)
|
1542
|
-
res['@language'] = (value.language || language(property)).to_s
|
1543
|
-
end
|
1544
|
-
res['@value'] = value.to_s
|
1545
|
-
end
|
1546
|
-
res
|
1547
|
-
else
|
1548
|
-
# Otherwise, initialize result to a JSON object with an @value member whose value is set to value.
|
1549
|
-
res = {}
|
1550
|
-
|
1551
|
-
if td.type_mapping && !CONTAINERS_ID_VOCAB.include?(td.type_mapping.to_s)
|
1552
|
-
res['@type'] = td.type_mapping.to_s
|
1553
|
-
elsif value.is_a?(String)
|
1554
|
-
language = language(property)
|
1555
|
-
direction = direction(property)
|
1556
|
-
res['@language'] = language if language
|
1557
|
-
res['@direction'] = direction if direction
|
1558
|
-
end
|
1559
|
-
|
1560
|
-
res.merge('@value' => value)
|
1561
|
-
end
|
1619
|
+
# 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.
|
1620
|
+
if value.is_a?(String) && td.type_mapping == '@vocab'
|
1621
|
+
return { '@id' => expand_iri(value, vocab: true, documentRelative: true, base: base).to_s }
|
1622
|
+
end
|
1562
1623
|
|
1563
|
-
|
1564
|
-
|
1565
|
-
|
1566
|
-
|
1624
|
+
case value
|
1625
|
+
when RDF::URI, RDF::Node
|
1626
|
+
{ '@id' => value.to_s }
|
1627
|
+
when Date, DateTime, Time
|
1628
|
+
lit = RDF::Literal.new(value)
|
1629
|
+
{ '@value' => lit.to_s, '@type' => lit.datatype.to_s }
|
1630
|
+
else
|
1631
|
+
# Otherwise, initialize result to a JSON object with an @value member whose value is set to value.
|
1632
|
+
res = {}
|
1633
|
+
|
1634
|
+
if td.type_mapping && !CONTAINERS_ID_VOCAB.include?(td.type_mapping.to_s)
|
1635
|
+
res['@type'] = td.type_mapping.to_s
|
1636
|
+
elsif value.is_a?(String)
|
1637
|
+
language = language(property)
|
1638
|
+
direction = direction(property)
|
1639
|
+
res['@language'] = language if language
|
1640
|
+
res['@direction'] = direction if direction
|
1641
|
+
end
|
1567
1642
|
|
1568
|
-
|
1569
|
-
|
1570
|
-
#
|
1571
|
-
# @param [String] property
|
1572
|
-
# Associated property used to find coercion rules
|
1573
|
-
# @param [Hash] value
|
1574
|
-
# Value (literal or IRI), in full object representation, to be compacted
|
1575
|
-
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1576
|
-
#
|
1577
|
-
# @return [Hash] Object representation of value
|
1578
|
-
# @raise [JsonLdError] if the iri cannot be expanded
|
1579
|
-
# @see https://www.w3.org/TR/json-ld11-api/#value-compaction
|
1580
|
-
# FIXME: revisit the specification version of this.
|
1581
|
-
def compact_value(property, value, base: nil)
|
1582
|
-
#log_debug("compact_value") {"property: #{property.inspect}, value: #{value.inspect}"}
|
1583
|
-
|
1584
|
-
indexing = index?(value) && container(property).include?('@index')
|
1585
|
-
language = language(property)
|
1586
|
-
direction = direction(property)
|
1587
|
-
|
1588
|
-
result = case
|
1589
|
-
when coerce(property) == '@id' && value.key?('@id') && (value.keys - %w(@id @index)).empty?
|
1590
|
-
# Compact an @id coercion
|
1591
|
-
#log_debug("") {" (@id & coerce)"}
|
1592
|
-
compact_iri(value['@id'], base: base)
|
1593
|
-
when coerce(property) == '@vocab' && value.key?('@id') && (value.keys - %w(@id @index)).empty?
|
1594
|
-
# Compact an @id coercion
|
1595
|
-
#log_debug("") {" (@id & coerce & vocab)"}
|
1596
|
-
compact_iri(value['@id'], vocab: true)
|
1597
|
-
when value.key?('@id')
|
1598
|
-
#log_debug("") {" (@id)"}
|
1599
|
-
# return value as is
|
1600
|
-
value
|
1601
|
-
when value['@type'] && value['@type'] == coerce(property)
|
1602
|
-
# Compact common datatype
|
1603
|
-
#log_debug("") {" (@type & coerce) == #{coerce(property)}"}
|
1604
|
-
value['@value']
|
1605
|
-
when coerce(property) == '@none' || value['@type']
|
1606
|
-
# use original expanded value
|
1607
|
-
value
|
1608
|
-
when !value['@value'].is_a?(String)
|
1609
|
-
#log_debug("") {" (native)"}
|
1610
|
-
indexing || !index?(value) ? value['@value'] : value
|
1611
|
-
when value['@language'].to_s.downcase == language.to_s.downcase && value['@direction'] == direction
|
1612
|
-
# Compact language and direction
|
1613
|
-
indexing || !index?(value) ? value['@value'] : value
|
1614
|
-
else
|
1615
|
-
value
|
1643
|
+
res.merge('@value' => value)
|
1644
|
+
end
|
1616
1645
|
end
|
1617
1646
|
|
1618
|
-
|
1619
|
-
|
1620
|
-
|
1621
|
-
|
1647
|
+
##
|
1648
|
+
# Compact a value
|
1649
|
+
#
|
1650
|
+
# @param [String] property
|
1651
|
+
# Associated property used to find coercion rules
|
1652
|
+
# @param [Hash] value
|
1653
|
+
# Value (literal or IRI), in full object representation, to be compacted
|
1654
|
+
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1655
|
+
#
|
1656
|
+
# @return [Hash] Object representation of value
|
1657
|
+
# @raise [JsonLdError] if the iri cannot be expanded
|
1658
|
+
# @see https://www.w3.org/TR/json-ld11-api/#value-compaction
|
1659
|
+
# FIXME: revisit the specification version of this.
|
1660
|
+
def compact_value(property, value, base: nil)
|
1661
|
+
# log_debug("compact_value") {"property: #{property.inspect}, value: #{value.inspect}"}
|
1662
|
+
|
1663
|
+
indexing = index?(value) && container(property).include?('@index')
|
1664
|
+
language = language(property)
|
1665
|
+
direction = direction(property)
|
1666
|
+
|
1667
|
+
result = if coerce(property) == '@id' && value.key?('@id') && (value.keys - %w[@id @index]).empty?
|
1668
|
+
# Compact an @id coercion
|
1669
|
+
# log_debug("") {" (@id & coerce)"}
|
1670
|
+
compact_iri(value['@id'], base: base)
|
1671
|
+
elsif coerce(property) == '@vocab' && value.key?('@id') && (value.keys - %w[@id @index]).empty?
|
1672
|
+
# Compact an @id coercion
|
1673
|
+
# log_debug("") {" (@id & coerce & vocab)"}
|
1674
|
+
compact_iri(value['@id'], vocab: true)
|
1675
|
+
elsif value.key?('@id')
|
1676
|
+
# log_debug("") {" (@id)"}
|
1677
|
+
# return value as is
|
1678
|
+
value
|
1679
|
+
elsif value['@type'] && value['@type'] == coerce(property)
|
1680
|
+
# Compact common datatype
|
1681
|
+
# log_debug("") {" (@type & coerce) == #{coerce(property)}"}
|
1682
|
+
value['@value']
|
1683
|
+
elsif coerce(property) == '@none' || value['@type']
|
1684
|
+
# use original expanded value
|
1685
|
+
value
|
1686
|
+
elsif !value['@value'].is_a?(String)
|
1687
|
+
# log_debug("") {" (native)"}
|
1688
|
+
indexing || !index?(value) ? value['@value'] : value
|
1689
|
+
elsif value['@language'].to_s.casecmp(language.to_s).zero? && value['@direction'] == direction
|
1690
|
+
# Compact language and direction
|
1691
|
+
indexing || !index?(value) ? value['@value'] : value
|
1622
1692
|
else
|
1623
|
-
|
1693
|
+
value
|
1624
1694
|
end
|
1625
|
-
result = result.merge('@type' => c_type)
|
1626
|
-
end
|
1627
|
-
|
1628
|
-
# If the result is an object, tranform keys using any term keyword aliases
|
1629
|
-
if result.is_a?(Hash) && result.keys.any? {|k| self.alias(k) != k}
|
1630
|
-
#log_debug("") {" (map to key aliases)"}
|
1631
|
-
new_element = {}
|
1632
|
-
result.each do |k, v|
|
1633
|
-
new_element[self.alias(k)] = v
|
1634
|
-
end
|
1635
|
-
result = new_element
|
1636
|
-
end
|
1637
1695
|
|
1638
|
-
|
1639
|
-
|
1640
|
-
|
1696
|
+
if result.is_a?(Hash) && result.key?('@type') && value['@type'] != '@json'
|
1697
|
+
# Compact values of @type
|
1698
|
+
c_type = if result['@type'].is_a?(Array)
|
1699
|
+
result['@type'].map { |t| compact_iri(t, vocab: true) }
|
1700
|
+
else
|
1701
|
+
compact_iri(result['@type'], vocab: true)
|
1702
|
+
end
|
1703
|
+
result = result.merge('@type' => c_type)
|
1704
|
+
end
|
1705
|
+
|
1706
|
+
# If the result is an object, tranform keys using any term keyword aliases
|
1707
|
+
if result.is_a?(Hash) && result.keys.any? { |k| self.alias(k) != k }
|
1708
|
+
# log_debug("") {" (map to key aliases)"}
|
1709
|
+
new_element = {}
|
1710
|
+
result.each do |k, v|
|
1711
|
+
new_element[self.alias(k)] = v
|
1712
|
+
end
|
1713
|
+
result = new_element
|
1714
|
+
end
|
1641
1715
|
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
1646
|
-
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1650
|
-
|
1651
|
-
|
1652
|
-
|
1653
|
-
|
1654
|
-
|
1655
|
-
|
1656
|
-
"
|
1657
|
-
|
1658
|
-
|
1659
|
-
|
1716
|
+
# log_debug("") {"=> #{result.inspect}"}
|
1717
|
+
result
|
1718
|
+
end
|
1719
|
+
|
1720
|
+
##
|
1721
|
+
# Turn this into a source for a new instantiation
|
1722
|
+
# @param [Array<String>] aliases
|
1723
|
+
# Other URLs to alias when preloading
|
1724
|
+
# @return [String]
|
1725
|
+
def to_rb(*aliases)
|
1726
|
+
canon_base = RDF::URI(context_base).canonicalize
|
1727
|
+
defn = []
|
1728
|
+
|
1729
|
+
defn << "base: #{base.to_s.inspect}" if base
|
1730
|
+
defn << "language: #{default_language.inspect}" if default_language
|
1731
|
+
defn << "vocab: #{vocab.to_s.inspect}" if vocab
|
1732
|
+
defn << "processingMode: #{processingMode.inspect}" if processingMode
|
1733
|
+
term_defs = term_definitions.map do |term, td|
|
1734
|
+
" " + term.inspect + " => " + td.to_rb
|
1735
|
+
end.sort
|
1736
|
+
defn << "term_definitions: {\n#{term_defs.join(",\n")}\n }" unless term_defs.empty?
|
1737
|
+
%(# -*- encoding: utf-8 -*-
|
1660
1738
|
# frozen_string_literal: true
|
1661
1739
|
# This file generated automatically from #{context_base}
|
1662
1740
|
require 'json/ld'
|
1663
1741
|
class JSON::LD::Context
|
1664
1742
|
).gsub(/^ /, '') +
|
1665
|
-
|
1666
|
-
|
1667
|
-
|
1668
|
-
|
1743
|
+
%[ add_preloaded("#{canon_base}") do\n new(] + defn.join(", ") + ")\n end\n" +
|
1744
|
+
aliases.map { |a| %[ alias_preloaded("#{a}", "#{canon_base}")\n] }.join +
|
1745
|
+
"end\n"
|
1746
|
+
end
|
1669
1747
|
|
1670
|
-
|
1671
|
-
|
1672
|
-
|
1673
|
-
|
1674
|
-
|
1675
|
-
|
1676
|
-
|
1677
|
-
|
1678
|
-
|
1679
|
-
|
1680
|
-
|
1748
|
+
def inspect
|
1749
|
+
v = %w([Context)
|
1750
|
+
v << "base=#{base}" if base
|
1751
|
+
v << "vocab=#{vocab}" if vocab
|
1752
|
+
v << "processingMode=#{processingMode}" if processingMode
|
1753
|
+
v << "default_language=#{default_language}" if default_language
|
1754
|
+
v << "default_direction=#{default_direction}" if default_direction
|
1755
|
+
v << "previous_context" if previous_context
|
1756
|
+
v << "term_definitions[#{term_definitions.length}]=#{term_definitions}"
|
1757
|
+
v.join(" ") + "]"
|
1758
|
+
end
|
1681
1759
|
|
1682
|
-
|
1683
|
-
|
1684
|
-
|
1685
|
-
|
1686
|
-
|
1687
|
-
|
1688
|
-
|
1689
|
-
|
1690
|
-
|
1691
|
-
|
1692
|
-
|
1693
|
-
|
1694
|
-
|
1695
|
-
|
1696
|
-
|
1760
|
+
# Duplicate an active context, allowing it to be modified.
|
1761
|
+
def dup
|
1762
|
+
that = self
|
1763
|
+
ec = Context.new(unfrozen: true, **@options)
|
1764
|
+
ec.context_base = that.context_base
|
1765
|
+
ec.base = that.base unless that.base.nil?
|
1766
|
+
ec.default_direction = that.default_direction
|
1767
|
+
ec.default_language = that.default_language
|
1768
|
+
ec.previous_context = that.previous_context
|
1769
|
+
ec.processingMode = that.processingMode if that.instance_variable_get(:@processingMode)
|
1770
|
+
ec.vocab = that.vocab if that.vocab
|
1771
|
+
|
1772
|
+
ec.instance_eval do
|
1773
|
+
@term_definitions = that.term_definitions.dup
|
1774
|
+
@iri_to_term = that.iri_to_term
|
1775
|
+
end
|
1776
|
+
ec
|
1697
1777
|
end
|
1698
|
-
ec
|
1699
|
-
end
|
1700
1778
|
|
1701
|
-
|
1779
|
+
protected
|
1702
1780
|
|
1703
|
-
|
1704
|
-
|
1705
|
-
|
1706
|
-
|
1707
|
-
|
1708
|
-
|
1709
|
-
|
1710
|
-
|
1711
|
-
|
1781
|
+
##
|
1782
|
+
# Determine if `term` is a suitable term.
|
1783
|
+
# Term may be any valid JSON string.
|
1784
|
+
#
|
1785
|
+
# @param [String] term
|
1786
|
+
# @return [Boolean]
|
1787
|
+
def term_valid?(term)
|
1788
|
+
term.is_a?(String) && !term.empty?
|
1789
|
+
end
|
1712
1790
|
|
1713
|
-
|
1714
|
-
|
1715
|
-
|
1716
|
-
|
1717
|
-
|
1718
|
-
|
1719
|
-
|
1720
|
-
|
1721
|
-
|
1722
|
-
|
1723
|
-
|
1724
|
-
|
1791
|
+
# Reverse term mapping, typically used for finding aliases for keys.
|
1792
|
+
#
|
1793
|
+
# Returns either the original value, or a mapping for this value.
|
1794
|
+
#
|
1795
|
+
# @example
|
1796
|
+
# {"@context": {"id": "@id"}, "@id": "foo"} => {"id": "foo"}
|
1797
|
+
#
|
1798
|
+
# @param [RDF::URI, String] value
|
1799
|
+
# @return [String]
|
1800
|
+
def alias(value)
|
1801
|
+
iri_to_term.fetch(value, value)
|
1802
|
+
end
|
1725
1803
|
|
1726
|
-
|
1727
|
-
|
1728
|
-
|
1729
|
-
|
1730
|
-
|
1731
|
-
|
1732
|
-
|
1733
|
-
|
1734
|
-
|
1735
|
-
|
1736
|
-
|
1737
|
-
|
1738
|
-
|
1739
|
-
|
1740
|
-
|
1741
|
-
|
1742
|
-
|
1743
|
-
|
1804
|
+
private
|
1805
|
+
|
1806
|
+
CONTEXT_CONTAINER_ARRAY_TERMS = Set.new(%w[@set @list @graph]).freeze
|
1807
|
+
CONTEXT_CONTAINER_ID_GRAPH = Set.new(%w[@id @graph]).freeze
|
1808
|
+
CONTEXT_CONTAINER_INDEX_GRAPH = Set.new(%w[@index @graph]).freeze
|
1809
|
+
CONTEXT_BASE_FRAG_OR_QUERY = %w[? #].freeze
|
1810
|
+
CONTEXT_TYPE_ID_VOCAB = %w[@id @vocab].freeze
|
1811
|
+
|
1812
|
+
##
|
1813
|
+
# Reads the `@context` from an IO
|
1814
|
+
def load_context(io, **options)
|
1815
|
+
io.rewind
|
1816
|
+
remote_doc = API.loadRemoteDocument(io, **options)
|
1817
|
+
if remote_doc.document.is_a?(String)
|
1818
|
+
MultiJson.load(remote_doc.document)
|
1819
|
+
else
|
1820
|
+
remote_doc.document
|
1821
|
+
end
|
1744
1822
|
end
|
1745
|
-
end
|
1746
1823
|
|
1747
|
-
|
1748
|
-
|
1749
|
-
|
1750
|
-
|
1751
|
-
|
1752
|
-
|
1753
|
-
|
1754
|
-
|
1824
|
+
def uri(value)
|
1825
|
+
case value.to_s
|
1826
|
+
when /^_:(.*)$/
|
1827
|
+
# Map BlankNodes if a namer is given
|
1828
|
+
# log_debug "uri(bnode)#{value}: #{$1}"
|
1829
|
+
bnode(namer.get_sym(::Regexp.last_match(1)))
|
1830
|
+
else
|
1831
|
+
RDF::URI(value)
|
1832
|
+
# value.validate! if options[:validate]
|
1833
|
+
|
1834
|
+
end
|
1835
|
+
end
|
1836
|
+
|
1837
|
+
# Keep track of allocated BNodes
|
1838
|
+
#
|
1839
|
+
# Don't actually use the name provided, to prevent name alias issues.
|
1840
|
+
# @return [RDF::Node]
|
1841
|
+
def bnode(value = nil)
|
1842
|
+
@@bnode_cache ||= {}
|
1843
|
+
@@bnode_cache[value.to_s] ||= RDF::Node.new(value)
|
1844
|
+
end
|
1845
|
+
|
1846
|
+
##
|
1847
|
+
# Inverse Context creation
|
1848
|
+
#
|
1849
|
+
# When there is more than one term that could be chosen to compact an IRI, it has to be ensured that the term selection is both deterministic and represents the most context-appropriate choice whilst taking into consideration algorithmic complexity.
|
1850
|
+
#
|
1851
|
+
# In order to make term selections, the concept of an inverse context is introduced. An inverse context is essentially a reverse lookup table that maps container mappings, type mappings, and language mappings to a simple term for a given active context. A inverse context only needs to be generated for an active context if it is being used for compaction.
|
1852
|
+
#
|
1853
|
+
# To make use of an inverse context, a list of preferred container mappings and the type mapping or language mapping are gathered for a particular value associated with an IRI. These parameters are then fed to the Term Selection algorithm, which will find the term that most appropriately matches the value's mappings.
|
1854
|
+
#
|
1855
|
+
# @example Basic structure of resulting inverse context
|
1856
|
+
# {
|
1857
|
+
# "http://example.com/term": {
|
1858
|
+
# "@language": {
|
1859
|
+
# "@null": "term",
|
1860
|
+
# "@none": "term",
|
1861
|
+
# "en": "term",
|
1862
|
+
# "ar_rtl": "term"
|
1863
|
+
# },
|
1864
|
+
# "@type": {
|
1865
|
+
# "@reverse": "term",
|
1866
|
+
# "@none": "term",
|
1867
|
+
# "http://datatype": "term"
|
1868
|
+
# },
|
1869
|
+
# "@any": {
|
1870
|
+
# "@none": "term",
|
1871
|
+
# }
|
1872
|
+
# }
|
1873
|
+
# }
|
1874
|
+
# @return [Hash{String => Hash{String => String}}]
|
1875
|
+
# @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
|
1876
|
+
def inverse_context
|
1877
|
+
Context.inverse_cache[hash] ||= begin
|
1878
|
+
result = {}
|
1879
|
+
default_language = (self.default_language || '@none').downcase
|
1880
|
+
term_definitions.keys.sort do |a, b|
|
1881
|
+
a.length == b.length ? (a <=> b) : (a.length <=> b.length)
|
1882
|
+
end.each do |term|
|
1883
|
+
next unless (td = term_definitions[term])
|
1884
|
+
|
1885
|
+
container = td.container_mapping.to_a.join
|
1886
|
+
if container.empty?
|
1887
|
+
container = td.as_set? ? %(@set) : %(@none)
|
1888
|
+
end
|
1755
1889
|
|
1756
|
-
|
1757
|
-
|
1758
|
-
|
1759
|
-
|
1760
|
-
|
1761
|
-
|
1762
|
-
|
1763
|
-
|
1764
|
-
|
1765
|
-
|
1766
|
-
|
1767
|
-
|
1768
|
-
|
1769
|
-
|
1770
|
-
|
1771
|
-
|
1772
|
-
|
1773
|
-
|
1774
|
-
|
1775
|
-
|
1776
|
-
|
1777
|
-
|
1778
|
-
|
1779
|
-
|
1780
|
-
|
1781
|
-
|
1782
|
-
|
1783
|
-
|
1784
|
-
|
1785
|
-
|
1786
|
-
|
1787
|
-
|
1788
|
-
|
1789
|
-
|
1790
|
-
|
1791
|
-
a.length == b.length ? (a <=> b) : (a.length <=> b.length)
|
1792
|
-
end.each do |term|
|
1793
|
-
next unless td = term_definitions[term]
|
1794
|
-
|
1795
|
-
container = td.container_mapping.to_a.join('')
|
1796
|
-
if container.empty?
|
1797
|
-
container = td.as_set? ? %(@set) : %(@none)
|
1798
|
-
end
|
1799
|
-
|
1800
|
-
container_map = result[td.id.to_s] ||= {}
|
1801
|
-
tl_map = container_map[container] ||= {'@language' => {}, '@type' => {}, '@any' => {}}
|
1802
|
-
type_map = tl_map['@type']
|
1803
|
-
language_map = tl_map['@language']
|
1804
|
-
any_map = tl_map['@any']
|
1805
|
-
any_map['@none'] ||= term
|
1806
|
-
if td.reverse_property
|
1807
|
-
type_map['@reverse'] ||= term
|
1808
|
-
elsif td.type_mapping == '@none'
|
1809
|
-
type_map['@any'] ||= term
|
1810
|
-
language_map['@any'] ||= term
|
1811
|
-
any_map['@any'] ||= term
|
1812
|
-
elsif td.type_mapping
|
1813
|
-
type_map[td.type_mapping.to_s] ||= term
|
1814
|
-
elsif !td.language_mapping.nil? && !td.direction_mapping.nil?
|
1815
|
-
lang_dir = if td.language_mapping && td.direction_mapping
|
1816
|
-
"#{td.language_mapping}_#{td.direction_mapping}".downcase
|
1817
|
-
elsif td.language_mapping
|
1818
|
-
td.language_mapping.downcase
|
1819
|
-
elsif td.direction_mapping
|
1820
|
-
"_#{td.direction_mapping}"
|
1890
|
+
container_map = result[td.id.to_s] ||= {}
|
1891
|
+
tl_map = container_map[container] ||= { '@language' => {}, '@type' => {}, '@any' => {} }
|
1892
|
+
type_map = tl_map['@type']
|
1893
|
+
language_map = tl_map['@language']
|
1894
|
+
any_map = tl_map['@any']
|
1895
|
+
any_map['@none'] ||= term
|
1896
|
+
if td.reverse_property
|
1897
|
+
type_map['@reverse'] ||= term
|
1898
|
+
elsif td.type_mapping == '@none'
|
1899
|
+
type_map['@any'] ||= term
|
1900
|
+
language_map['@any'] ||= term
|
1901
|
+
any_map['@any'] ||= term
|
1902
|
+
elsif td.type_mapping
|
1903
|
+
type_map[td.type_mapping.to_s] ||= term
|
1904
|
+
elsif !td.language_mapping.nil? && !td.direction_mapping.nil?
|
1905
|
+
lang_dir = if td.language_mapping && td.direction_mapping
|
1906
|
+
"#{td.language_mapping}_#{td.direction_mapping}".downcase
|
1907
|
+
elsif td.language_mapping
|
1908
|
+
td.language_mapping.downcase
|
1909
|
+
elsif td.direction_mapping
|
1910
|
+
"_#{td.direction_mapping}"
|
1911
|
+
else
|
1912
|
+
"@null"
|
1913
|
+
end
|
1914
|
+
language_map[lang_dir] ||= term
|
1915
|
+
elsif !td.language_mapping.nil?
|
1916
|
+
lang_dir = (td.language_mapping || '@null').downcase
|
1917
|
+
language_map[lang_dir] ||= term
|
1918
|
+
elsif !td.direction_mapping.nil?
|
1919
|
+
lang_dir = td.direction_mapping ? "_#{td.direction_mapping}" : '@none'
|
1920
|
+
language_map[lang_dir] ||= term
|
1921
|
+
elsif default_direction
|
1922
|
+
language_map["_#{default_direction}"] ||= term
|
1923
|
+
language_map['@none'] ||= term
|
1924
|
+
type_map['@none'] ||= term
|
1821
1925
|
else
|
1822
|
-
|
1926
|
+
language_map[default_language] ||= term
|
1927
|
+
language_map['@none'] ||= term
|
1928
|
+
type_map['@none'] ||= term
|
1823
1929
|
end
|
1824
|
-
language_map[lang_dir] ||= term
|
1825
|
-
elsif !td.language_mapping.nil?
|
1826
|
-
lang_dir = (td.language_mapping || '@null').downcase
|
1827
|
-
language_map[lang_dir] ||= term
|
1828
|
-
elsif !td.direction_mapping.nil?
|
1829
|
-
lang_dir = td.direction_mapping ? "_#{td.direction_mapping}" : '@none'
|
1830
|
-
language_map[lang_dir] ||= term
|
1831
|
-
elsif default_direction
|
1832
|
-
language_map["_#{default_direction}"] ||= term
|
1833
|
-
language_map['@none'] ||= term
|
1834
|
-
type_map['@none'] ||= term
|
1835
|
-
else
|
1836
|
-
language_map[default_language] ||= term
|
1837
|
-
language_map['@none'] ||= term
|
1838
|
-
type_map['@none'] ||= term
|
1839
1930
|
end
|
1931
|
+
result
|
1840
1932
|
end
|
1841
|
-
result
|
1842
1933
|
end
|
1843
|
-
end
|
1844
1934
|
|
1845
|
-
|
1846
|
-
|
1847
|
-
|
1848
|
-
|
1849
|
-
|
1850
|
-
|
1851
|
-
|
1852
|
-
|
1853
|
-
|
1854
|
-
|
1855
|
-
|
1856
|
-
|
1857
|
-
|
1858
|
-
|
1859
|
-
|
1860
|
-
|
1861
|
-
|
1862
|
-
|
1863
|
-
|
1864
|
-
|
1865
|
-
|
1866
|
-
|
1867
|
-
|
1868
|
-
|
1869
|
-
|
1870
|
-
|
1871
|
-
|
1872
|
-
|
1935
|
+
##
|
1936
|
+
# This algorithm, invoked via the IRI Compaction algorithm, makes use of an active context's inverse context to find the term that is best used to compact an IRI. Other information about a value associated with the IRI is given, including which container mappings and which type mapping or language mapping would be best used to express the value.
|
1937
|
+
#
|
1938
|
+
# @param [String] iri
|
1939
|
+
# @param [Array<String>] containers
|
1940
|
+
# represents an ordered list of preferred container mappings
|
1941
|
+
# @param [String] type_language
|
1942
|
+
# indicates whether to look for a term with a matching type mapping or language mapping
|
1943
|
+
# @param [Array<String>] preferred_values
|
1944
|
+
# for the type mapping or language mapping
|
1945
|
+
# @return [String]
|
1946
|
+
def select_term(iri, containers, type_language, preferred_values)
|
1947
|
+
# log_debug("select_term") {
|
1948
|
+
# "iri: #{iri.inspect}, " +
|
1949
|
+
# "containers: #{containers.inspect}, " +
|
1950
|
+
# "type_language: #{type_language.inspect}, " +
|
1951
|
+
# "preferred_values: #{preferred_values.inspect}"
|
1952
|
+
# }
|
1953
|
+
container_map = inverse_context[iri]
|
1954
|
+
# log_debug(" ") {"container_map: #{container_map.inspect}"}
|
1955
|
+
containers.each do |container|
|
1956
|
+
next unless container_map.key?(container)
|
1957
|
+
|
1958
|
+
tl_map = container_map[container]
|
1959
|
+
value_map = tl_map[type_language]
|
1960
|
+
preferred_values.each do |item|
|
1961
|
+
next unless value_map.key?(item)
|
1962
|
+
|
1963
|
+
# log_debug("=>") {value_map[item].inspect}
|
1964
|
+
return value_map[item]
|
1965
|
+
end
|
1873
1966
|
end
|
1967
|
+
# log_debug("=>") {"nil"}
|
1968
|
+
nil
|
1874
1969
|
end
|
1875
|
-
#log_debug("=>") {"nil"}
|
1876
|
-
nil
|
1877
|
-
end
|
1878
1970
|
|
1879
|
-
|
1880
|
-
|
1881
|
-
|
1882
|
-
|
1883
|
-
|
1884
|
-
|
1885
|
-
|
1886
|
-
|
1887
|
-
|
1888
|
-
|
1889
|
-
|
1890
|
-
|
1891
|
-
|
1892
|
-
|
1893
|
-
|
1894
|
-
|
1895
|
-
|
1971
|
+
##
|
1972
|
+
# Removes a base IRI from the given absolute IRI.
|
1973
|
+
#
|
1974
|
+
# @param [String] base the base used for making `iri` relative
|
1975
|
+
# @param [String] iri the absolute IRI
|
1976
|
+
# @return [String]
|
1977
|
+
# the relative IRI if relative to base, otherwise the absolute IRI.
|
1978
|
+
def remove_base(base, iri)
|
1979
|
+
return iri unless base
|
1980
|
+
|
1981
|
+
@base_and_parents ||= begin
|
1982
|
+
u = base
|
1983
|
+
iri_set = u.to_s.end_with?('/') ? [u.to_s] : []
|
1984
|
+
iri_set << u.to_s while u != './' && (u = u.parent)
|
1985
|
+
iri_set
|
1986
|
+
end
|
1987
|
+
b = base.to_s
|
1988
|
+
return iri[b.length..] if iri.start_with?(b) && CONTEXT_BASE_FRAG_OR_QUERY.include?(iri[b.length, 1])
|
1896
1989
|
|
1897
|
-
|
1898
|
-
|
1899
|
-
|
1900
|
-
|
1990
|
+
@base_and_parents.each_with_index do |bb, index|
|
1991
|
+
next unless iri.start_with?(bb)
|
1992
|
+
|
1993
|
+
rel = ("../" * index) + iri[bb.length..]
|
1994
|
+
return rel.empty? ? "./" : rel
|
1995
|
+
end
|
1996
|
+
iri
|
1901
1997
|
end
|
1902
|
-
iri
|
1903
|
-
end
|
1904
1998
|
|
1905
|
-
|
1906
|
-
|
1907
|
-
|
1908
|
-
|
1909
|
-
|
1910
|
-
|
1911
|
-
|
1912
|
-
|
1999
|
+
## Used for testing
|
2000
|
+
# Retrieve term mappings
|
2001
|
+
#
|
2002
|
+
# @return [Array<RDF::URI>]
|
2003
|
+
def mappings
|
2004
|
+
{}.tap do |memo|
|
2005
|
+
term_definitions.each_pair do |t, td|
|
2006
|
+
memo[t] = td ? td.id : nil
|
2007
|
+
end
|
1913
2008
|
end
|
1914
2009
|
end
|
1915
|
-
end
|
1916
2010
|
|
1917
|
-
|
1918
|
-
|
1919
|
-
|
1920
|
-
|
1921
|
-
|
1922
|
-
|
1923
|
-
|
1924
|
-
|
1925
|
-
|
2011
|
+
## Used for testing
|
2012
|
+
# Retrieve term mapping
|
2013
|
+
#
|
2014
|
+
# @param [String, #to_s] term
|
2015
|
+
#
|
2016
|
+
# @return [RDF::URI, String]
|
2017
|
+
def mapping(term)
|
2018
|
+
term_definitions[term]&.id
|
2019
|
+
end
|
1926
2020
|
|
1927
|
-
|
1928
|
-
|
1929
|
-
|
1930
|
-
|
1931
|
-
|
1932
|
-
|
1933
|
-
|
1934
|
-
|
1935
|
-
|
2021
|
+
## Used for testing
|
2022
|
+
# Retrieve language mappings
|
2023
|
+
#
|
2024
|
+
# @return [Array<String>]
|
2025
|
+
# @deprecated
|
2026
|
+
def languages
|
2027
|
+
{}.tap do |memo|
|
2028
|
+
term_definitions.each_pair do |t, td|
|
2029
|
+
memo[t] = td.language_mapping
|
2030
|
+
end
|
1936
2031
|
end
|
1937
2032
|
end
|
1938
|
-
end
|
1939
2033
|
|
1940
|
-
|
1941
|
-
|
1942
|
-
|
1943
|
-
|
1944
|
-
|
1945
|
-
|
1946
|
-
|
2034
|
+
# Ensure @container mapping is appropriate
|
2035
|
+
# The result is the original container definition. For IRI containers, this is necessary to be able to determine the @type mapping for string values
|
2036
|
+
def check_container(container, _local_context, _defined, term)
|
2037
|
+
if container.is_a?(Array) && processingMode('json-ld-1.0')
|
2038
|
+
raise JsonLdError::InvalidContainerMapping,
|
2039
|
+
"'@container' on term #{term.inspect} must be a string: #{container.inspect}"
|
2040
|
+
end
|
1947
2041
|
|
1948
|
-
|
1949
|
-
|
1950
|
-
|
1951
|
-
|
1952
|
-
|
1953
|
-
|
1954
|
-
|
1955
|
-
|
1956
|
-
|
1957
|
-
|
1958
|
-
|
1959
|
-
|
1960
|
-
|
1961
|
-
|
1962
|
-
val.length == 1
|
1963
|
-
|
1964
|
-
|
1965
|
-
|
1966
|
-
|
1967
|
-
|
1968
|
-
|
1969
|
-
|
1970
|
-
|
1971
|
-
|
1972
|
-
|
1973
|
-
|
1974
|
-
"
|
1975
|
-
|
1976
|
-
|
1977
|
-
|
1978
|
-
|
1979
|
-
|
1980
|
-
elsif val.include?('@type') || val.include?('@graph')
|
1981
|
-
raise JsonLdError::InvalidContainerMapping,
|
1982
|
-
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" if
|
1983
|
-
processingMode('json-ld-1.0')
|
1984
|
-
raise JsonLdError::InvalidContainerMapping,
|
1985
|
-
"'@container' on term #{term.inspect} using @language cannot have any values other than @set, found #{container.inspect}" unless
|
1986
|
-
val.length == 1
|
1987
|
-
# Okay
|
1988
|
-
elsif val.empty?
|
1989
|
-
# Okay
|
1990
|
-
else
|
1991
|
-
raise JsonLdError::InvalidContainerMapping,
|
2042
|
+
val = Set.new(Array(container))
|
2043
|
+
val.delete('@set') if (has_set = val.include?('@set'))
|
2044
|
+
|
2045
|
+
if val.include?('@list')
|
2046
|
+
unless !has_set && val.length == 1
|
2047
|
+
raise JsonLdError::InvalidContainerMapping,
|
2048
|
+
"'@container' on term #{term.inspect} using @list cannot have any other values"
|
2049
|
+
end
|
2050
|
+
# Okay
|
2051
|
+
elsif val.include?('@language')
|
2052
|
+
if has_set && processingMode('json-ld-1.0')
|
2053
|
+
raise JsonLdError::InvalidContainerMapping,
|
2054
|
+
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}"
|
2055
|
+
end
|
2056
|
+
unless val.length == 1
|
2057
|
+
raise JsonLdError::InvalidContainerMapping,
|
2058
|
+
"'@container' on term #{term.inspect} using @language cannot have any values other than @set, found #{container.inspect}"
|
2059
|
+
end
|
2060
|
+
# Okay
|
2061
|
+
elsif val.include?('@index')
|
2062
|
+
if has_set && processingMode('json-ld-1.0')
|
2063
|
+
raise JsonLdError::InvalidContainerMapping,
|
2064
|
+
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}"
|
2065
|
+
end
|
2066
|
+
unless (val - CONTEXT_CONTAINER_INDEX_GRAPH).empty?
|
2067
|
+
raise JsonLdError::InvalidContainerMapping,
|
2068
|
+
"'@container' on term #{term.inspect} using @index cannot have any values other than @set and/or @graph, found #{container.inspect}"
|
2069
|
+
end
|
2070
|
+
# Okay
|
2071
|
+
elsif val.include?('@id')
|
2072
|
+
if processingMode('json-ld-1.0')
|
2073
|
+
raise JsonLdError::InvalidContainerMapping,
|
1992
2074
|
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}"
|
2075
|
+
end
|
2076
|
+
unless val.subset?(CONTEXT_CONTAINER_ID_GRAPH)
|
2077
|
+
raise JsonLdError::InvalidContainerMapping,
|
2078
|
+
"'@container' on term #{term.inspect} using @id cannot have any values other than @set and/or @graph, found #{container.inspect}"
|
2079
|
+
end
|
2080
|
+
# Okay
|
2081
|
+
elsif val.include?('@type') || val.include?('@graph')
|
2082
|
+
if processingMode('json-ld-1.0')
|
2083
|
+
raise JsonLdError::InvalidContainerMapping,
|
2084
|
+
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}"
|
2085
|
+
end
|
2086
|
+
unless val.length == 1
|
2087
|
+
raise JsonLdError::InvalidContainerMapping,
|
2088
|
+
"'@container' on term #{term.inspect} using @language cannot have any values other than @set, found #{container.inspect}"
|
2089
|
+
end
|
2090
|
+
# Okay
|
2091
|
+
elsif val.empty?
|
2092
|
+
# Okay
|
2093
|
+
else
|
2094
|
+
raise JsonLdError::InvalidContainerMapping,
|
2095
|
+
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}"
|
2096
|
+
end
|
2097
|
+
Array(container)
|
1993
2098
|
end
|
1994
|
-
Array(container)
|
1995
|
-
end
|
1996
2099
|
|
1997
|
-
|
1998
|
-
|
1999
|
-
|
2000
|
-
|
2100
|
+
# Term Definitions specify how properties and values have to be interpreted as well as the current vocabulary mapping and the default language
|
2101
|
+
class TermDefinition
|
2102
|
+
# @return [RDF::URI] IRI map
|
2103
|
+
attr_accessor :id
|
2001
2104
|
|
2002
|
-
|
2003
|
-
|
2105
|
+
# @return [String] term name
|
2106
|
+
attr_accessor :term
|
2004
2107
|
|
2005
|
-
|
2006
|
-
|
2108
|
+
# @return [String] Type mapping
|
2109
|
+
attr_accessor :type_mapping
|
2007
2110
|
|
2008
|
-
|
2009
|
-
|
2010
|
-
|
2111
|
+
# Base container mapping, without @set
|
2112
|
+
# @return [Array<'@index', '@language', '@index', '@set', '@type', '@id', '@graph'>] Container mapping
|
2113
|
+
attr_reader :container_mapping
|
2011
2114
|
|
2012
|
-
|
2013
|
-
|
2115
|
+
# @return [String] Term used for nest properties
|
2116
|
+
attr_accessor :nest
|
2014
2117
|
|
2015
|
-
|
2016
|
-
|
2017
|
-
|
2118
|
+
# Language mapping of term, `false` is used if there is an explicit language mapping for this term.
|
2119
|
+
# @return [String] Language mapping
|
2120
|
+
attr_accessor :language_mapping
|
2018
2121
|
|
2019
|
-
|
2020
|
-
|
2021
|
-
|
2122
|
+
# Direction of term, `false` is used if there is explicit direction mapping mapping for this term.
|
2123
|
+
# @return ["ltr", "rtl"] direction_mapping
|
2124
|
+
attr_accessor :direction_mapping
|
2022
2125
|
|
2023
|
-
|
2024
|
-
|
2126
|
+
# @return [Boolean] Reverse Property
|
2127
|
+
attr_accessor :reverse_property
|
2025
2128
|
|
2026
|
-
|
2027
|
-
|
2028
|
-
|
2129
|
+
# This is a simple term definition, not an expanded term definition
|
2130
|
+
# @return [Boolean]
|
2131
|
+
attr_accessor :simple
|
2029
2132
|
|
2030
|
-
|
2031
|
-
|
2032
|
-
|
2133
|
+
# Property used for data indexing; defaults to @index
|
2134
|
+
# @return [Boolean]
|
2135
|
+
attr_accessor :index
|
2033
2136
|
|
2034
|
-
|
2035
|
-
|
2137
|
+
# Indicate that term may be used as a prefix
|
2138
|
+
attr_writer :prefix
|
2036
2139
|
|
2037
|
-
|
2038
|
-
|
2039
|
-
|
2140
|
+
# Term-specific context
|
2141
|
+
# @return [Hash{String => Object}]
|
2142
|
+
attr_accessor :context
|
2040
2143
|
|
2041
|
-
|
2042
|
-
|
2043
|
-
|
2144
|
+
# Term is protected.
|
2145
|
+
# @return [Boolean]
|
2146
|
+
attr_writer :protected
|
2044
2147
|
|
2045
|
-
|
2046
|
-
|
2047
|
-
|
2148
|
+
# This is a simple term definition, not an expanded term definition
|
2149
|
+
# @return [Boolean] simple
|
2150
|
+
def simple?
|
2151
|
+
simple
|
2152
|
+
end
|
2048
2153
|
|
2049
|
-
|
2050
|
-
|
2051
|
-
|
2154
|
+
# This is an appropriate term to use as the prefix of a compact IRI
|
2155
|
+
# @return [Boolean] simple
|
2156
|
+
def prefix?
|
2157
|
+
@prefix
|
2158
|
+
end
|
2052
2159
|
|
2053
|
-
|
2054
|
-
|
2055
|
-
|
2056
|
-
|
2057
|
-
|
2058
|
-
|
2059
|
-
|
2060
|
-
|
2061
|
-
|
2062
|
-
|
2063
|
-
|
2064
|
-
|
2065
|
-
|
2066
|
-
|
2067
|
-
|
2068
|
-
|
2069
|
-
|
2070
|
-
|
2071
|
-
|
2072
|
-
|
2073
|
-
|
2074
|
-
|
2075
|
-
|
2076
|
-
|
2077
|
-
|
2078
|
-
|
2079
|
-
|
2080
|
-
|
2081
|
-
|
2082
|
-
|
2083
|
-
|
2084
|
-
|
2085
|
-
|
2086
|
-
|
2087
|
-
|
2088
|
-
|
2089
|
-
|
2090
|
-
|
2091
|
-
|
2092
|
-
|
2093
|
-
|
2094
|
-
|
2095
|
-
|
2160
|
+
# Create a new Term Mapping with an ID
|
2161
|
+
# @param [String] term
|
2162
|
+
# @param [String] id
|
2163
|
+
# @param [String] type_mapping Type mapping
|
2164
|
+
# @param [Set<'@index', '@language', '@index', '@set', '@type', '@id', '@graph'>] container_mapping
|
2165
|
+
# @param [String] language_mapping
|
2166
|
+
# Language mapping of term, `false` is used if there is an explicit language mapping for this term
|
2167
|
+
# @param ["ltr", "rtl"] direction_mapping
|
2168
|
+
# Direction mapping of term, `false` is used if there is an explicit direction mapping for this term
|
2169
|
+
# @param [Boolean] reverse_property
|
2170
|
+
# @param [Boolean] protected mark resulting context as protected
|
2171
|
+
# @param [String] nest term used for nest properties
|
2172
|
+
# @param [Boolean] simple
|
2173
|
+
# This is a simple term definition, not an expanded term definition
|
2174
|
+
# @param [Boolean] prefix
|
2175
|
+
# Term may be used as a prefix
|
2176
|
+
def initialize(term,
|
2177
|
+
id: nil,
|
2178
|
+
index: nil,
|
2179
|
+
type_mapping: nil,
|
2180
|
+
container_mapping: nil,
|
2181
|
+
language_mapping: nil,
|
2182
|
+
direction_mapping: nil,
|
2183
|
+
reverse_property: false,
|
2184
|
+
nest: nil,
|
2185
|
+
protected: nil,
|
2186
|
+
simple: false,
|
2187
|
+
prefix: nil,
|
2188
|
+
context: nil)
|
2189
|
+
@term = term
|
2190
|
+
@id = id.to_s unless id.nil?
|
2191
|
+
@index = index.to_s unless index.nil?
|
2192
|
+
@type_mapping = type_mapping.to_s unless type_mapping.nil?
|
2193
|
+
self.container_mapping = container_mapping
|
2194
|
+
@language_mapping = language_mapping unless language_mapping.nil?
|
2195
|
+
@direction_mapping = direction_mapping unless direction_mapping.nil?
|
2196
|
+
@reverse_property = reverse_property
|
2197
|
+
@protected = protected
|
2198
|
+
@nest = nest unless nest.nil?
|
2199
|
+
@simple = simple
|
2200
|
+
@prefix = prefix unless prefix.nil?
|
2201
|
+
@context = context unless context.nil?
|
2202
|
+
end
|
2096
2203
|
|
2097
|
-
|
2098
|
-
|
2099
|
-
|
2100
|
-
|
2101
|
-
# Set container mapping, from an array which may include @set
|
2102
|
-
def container_mapping=(mapping)
|
2103
|
-
mapping = case mapping
|
2104
|
-
when Set then mapping
|
2105
|
-
when Array then Set.new(mapping)
|
2106
|
-
when String then Set[mapping]
|
2107
|
-
when nil then Set.new
|
2108
|
-
else
|
2109
|
-
raise "Shouldn't happen with #{mapping.inspect}"
|
2204
|
+
# Term is protected.
|
2205
|
+
# @return [Boolean]
|
2206
|
+
def protected?
|
2207
|
+
!!@protected
|
2110
2208
|
end
|
2111
|
-
|
2112
|
-
|
2113
|
-
|
2209
|
+
|
2210
|
+
# Returns true if the term matches a IRI
|
2211
|
+
#
|
2212
|
+
# @param iri [String] the IRI
|
2213
|
+
# @return [Boolean]
|
2214
|
+
def match_iri?(iri)
|
2215
|
+
iri.start_with?(id)
|
2114
2216
|
end
|
2115
|
-
@container_mapping = mapping
|
2116
|
-
@index ||= '@index' if mapping.include?('@index')
|
2117
|
-
end
|
2118
2217
|
|
2119
|
-
|
2120
|
-
|
2121
|
-
|
2122
|
-
|
2123
|
-
|
2124
|
-
|
2125
|
-
cid = if context.vocab && id.start_with?(context.vocab)
|
2126
|
-
# Nothing to return unless it's the same as the vocab
|
2127
|
-
id == context.vocab ? context.vocab : id.to_s[context.vocab.length..-1]
|
2128
|
-
else
|
2129
|
-
# Find a term to act as a prefix
|
2130
|
-
iri, prefix = context.iri_to_term.detect {|i,p| id.to_s.start_with?(i.to_s)}
|
2131
|
-
iri && iri != id ? "#{prefix}:#{id.to_s[iri.length..-1]}" : id
|
2218
|
+
# Returns true if the term matches a compact IRI
|
2219
|
+
#
|
2220
|
+
# @param iri [String] the compact IRI
|
2221
|
+
# @return [Boolean]
|
2222
|
+
def match_compact_iri?(iri)
|
2223
|
+
iri.start_with?(prefix_colon)
|
2132
2224
|
end
|
2133
2225
|
|
2134
|
-
|
2135
|
-
|
2136
|
-
|
2137
|
-
|
2138
|
-
|
2139
|
-
|
2140
|
-
|
2141
|
-
|
2142
|
-
|
2143
|
-
|
2226
|
+
# Set container mapping, from an array which may include @set
|
2227
|
+
def container_mapping=(mapping)
|
2228
|
+
mapping = case mapping
|
2229
|
+
when Set then mapping
|
2230
|
+
when Array then Set.new(mapping)
|
2231
|
+
when String then Set[mapping]
|
2232
|
+
when nil then Set.new
|
2233
|
+
else
|
2234
|
+
raise "Shouldn't happen with #{mapping.inspect}"
|
2235
|
+
end
|
2236
|
+
if (@as_set = mapping.include?('@set'))
|
2237
|
+
mapping = mapping.dup
|
2238
|
+
mapping.delete('@set')
|
2239
|
+
end
|
2240
|
+
@container_mapping = mapping
|
2241
|
+
@index ||= '@index' if mapping.include?('@index')
|
2242
|
+
end
|
2243
|
+
|
2244
|
+
##
|
2245
|
+
# Output Hash or String definition for this definition considering @language and @vocab
|
2246
|
+
#
|
2247
|
+
# @param [Context] context
|
2248
|
+
# @return [String, Hash{String => Array[String], String}]
|
2249
|
+
def to_context_definition(context)
|
2250
|
+
cid = if context.vocab && id.start_with?(context.vocab)
|
2251
|
+
# Nothing to return unless it's the same as the vocab
|
2252
|
+
id == context.vocab ? context.vocab : id.to_s[context.vocab.length..]
|
2253
|
+
else
|
2254
|
+
# Find a term to act as a prefix
|
2255
|
+
iri, prefix = context.iri_to_term.detect { |i, _p| id.to_s.start_with?(i.to_s) }
|
2256
|
+
iri && iri != id ? "#{prefix}:#{id.to_s[iri.length..]}" : id
|
2257
|
+
end
|
2258
|
+
|
2259
|
+
if simple?
|
2260
|
+
cid.to_s unless cid == term && context.vocab
|
2261
|
+
else
|
2262
|
+
defn = {}
|
2263
|
+
defn[reverse_property ? '@reverse' : '@id'] = cid.to_s unless cid == term && !reverse_property
|
2264
|
+
if type_mapping
|
2265
|
+
defn['@type'] = if KEYWORDS.include?(type_mapping)
|
2266
|
+
type_mapping
|
2267
|
+
else
|
2268
|
+
context.compact_iri(type_mapping, vocab: true)
|
2269
|
+
end
|
2270
|
+
end
|
2271
|
+
|
2272
|
+
cm = Array(container_mapping)
|
2273
|
+
cm << "@set" if as_set? && !cm.include?("@set")
|
2274
|
+
cm = cm.first if cm.length == 1
|
2275
|
+
defn['@container'] = cm unless cm.empty?
|
2276
|
+
# Language set as false to be output as null
|
2277
|
+
defn['@language'] = (@language_mapping || nil) unless @language_mapping.nil?
|
2278
|
+
defn['@direction'] = (@direction_mapping || nil) unless @direction_mapping.nil?
|
2279
|
+
defn['@context'] = @context if @context
|
2280
|
+
defn['@nest'] = @nest if @nest
|
2281
|
+
defn['@index'] = @index if @index
|
2282
|
+
defn['@prefix'] = @prefix unless @prefix.nil?
|
2283
|
+
defn
|
2284
|
+
end
|
2285
|
+
end
|
2286
|
+
|
2287
|
+
##
|
2288
|
+
# Turn this into a source for a new instantiation
|
2289
|
+
# FIXME: context serialization
|
2290
|
+
# @return [String]
|
2291
|
+
def to_rb
|
2292
|
+
defn = [%(TermDefinition.new\(#{term.inspect})]
|
2293
|
+
%w[id index type_mapping container_mapping language_mapping direction_mapping reverse_property nest simple
|
2294
|
+
prefix context protected].each do |acc|
|
2295
|
+
v = instance_variable_get("@#{acc}".to_sym)
|
2296
|
+
v = v.to_s if v.is_a?(RDF::Term)
|
2297
|
+
if acc == 'container_mapping'
|
2298
|
+
v = v.to_a
|
2299
|
+
v << '@set' if as_set?
|
2300
|
+
v = v.first if v.length <= 1
|
2144
2301
|
end
|
2302
|
+
defn << "#{acc}: #{v.inspect}" if v
|
2145
2303
|
end
|
2304
|
+
defn.join(', ') + ")"
|
2305
|
+
end
|
2146
2306
|
|
2147
|
-
|
2148
|
-
|
2149
|
-
|
2150
|
-
|
2151
|
-
# Language set as false to be output as null
|
2152
|
-
defn['@language'] = (@language_mapping ? @language_mapping : nil) unless @language_mapping.nil?
|
2153
|
-
defn['@direction'] = (@direction_mapping ? @direction_mapping : nil) unless @direction_mapping.nil?
|
2154
|
-
defn['@context'] = @context if @context
|
2155
|
-
defn['@nest'] = @nest if @nest
|
2156
|
-
defn['@index'] = @index if @index
|
2157
|
-
defn['@prefix'] = @prefix unless @prefix.nil?
|
2158
|
-
defn
|
2307
|
+
# If container mapping was defined along with @set
|
2308
|
+
# @return [Boolean]
|
2309
|
+
def as_set?
|
2310
|
+
@as_set || false
|
2159
2311
|
end
|
2160
|
-
end
|
2161
2312
|
|
2162
|
-
|
2163
|
-
|
2164
|
-
|
2165
|
-
|
2166
|
-
|
2167
|
-
|
2168
|
-
|
2169
|
-
|
2170
|
-
|
2171
|
-
|
2172
|
-
|
2173
|
-
|
2174
|
-
|
2175
|
-
|
2176
|
-
|
2177
|
-
|
2178
|
-
|
2179
|
-
|
2313
|
+
# Check if term definitions are identical, modulo @protected
|
2314
|
+
# @return [Boolean]
|
2315
|
+
def ==(other)
|
2316
|
+
other.is_a?(TermDefinition) &&
|
2317
|
+
id == other.id &&
|
2318
|
+
term == other.term &&
|
2319
|
+
type_mapping == other.type_mapping &&
|
2320
|
+
container_mapping == other.container_mapping &&
|
2321
|
+
nest == other.nest &&
|
2322
|
+
language_mapping == other.language_mapping &&
|
2323
|
+
direction_mapping == other.direction_mapping &&
|
2324
|
+
reverse_property == other.reverse_property &&
|
2325
|
+
simple == other.simple &&
|
2326
|
+
index == other.index &&
|
2327
|
+
context == other.context &&
|
2328
|
+
prefix? == other.prefix? &&
|
2329
|
+
as_set? == other.as_set?
|
2330
|
+
end
|
2180
2331
|
|
2181
|
-
|
2182
|
-
|
2183
|
-
|
2332
|
+
def inspect
|
2333
|
+
v = %w([TD)
|
2334
|
+
v << "id=#{@id}"
|
2335
|
+
v << "index=#{index.inspect}" unless index.nil?
|
2336
|
+
v << "term=#{@term}"
|
2337
|
+
v << "rev" if reverse_property
|
2338
|
+
v << "container=#{container_mapping}" if container_mapping
|
2339
|
+
v << "as_set=#{as_set?.inspect}"
|
2340
|
+
v << "lang=#{language_mapping.inspect}" unless language_mapping.nil?
|
2341
|
+
v << "dir=#{direction_mapping.inspect}" unless direction_mapping.nil?
|
2342
|
+
v << "type=#{type_mapping}" unless type_mapping.nil?
|
2343
|
+
v << "nest=#{nest.inspect}" unless nest.nil?
|
2344
|
+
v << "simple=true" if @simple
|
2345
|
+
v << "protected=true" if @protected
|
2346
|
+
v << "prefix=#{@prefix.inspect}" unless @prefix.nil?
|
2347
|
+
v << "has-context" unless context.nil?
|
2348
|
+
v.join(" ") + "]"
|
2349
|
+
end
|
2184
2350
|
|
2185
|
-
|
2186
|
-
# @return [Boolean]
|
2187
|
-
def ==(other)
|
2188
|
-
other.is_a?(TermDefinition) &&
|
2189
|
-
id == other.id &&
|
2190
|
-
term == other.term &&
|
2191
|
-
type_mapping == other.type_mapping &&
|
2192
|
-
container_mapping == other.container_mapping &&
|
2193
|
-
nest == other.nest &&
|
2194
|
-
language_mapping == other.language_mapping &&
|
2195
|
-
direction_mapping == other.direction_mapping &&
|
2196
|
-
reverse_property == other.reverse_property &&
|
2197
|
-
simple == other.simple &&
|
2198
|
-
index == other.index &&
|
2199
|
-
context == other.context &&
|
2200
|
-
prefix? == other.prefix? &&
|
2201
|
-
as_set? == other.as_set?
|
2202
|
-
end
|
2351
|
+
private
|
2203
2352
|
|
2204
|
-
|
2205
|
-
|
2206
|
-
|
2207
|
-
v << "index=#{index.inspect}" unless index.nil?
|
2208
|
-
v << "term=#{@term}"
|
2209
|
-
v << "rev" if reverse_property
|
2210
|
-
v << "container=#{container_mapping}" if container_mapping
|
2211
|
-
v << "as_set=#{as_set?.inspect}"
|
2212
|
-
v << "lang=#{language_mapping.inspect}" unless language_mapping.nil?
|
2213
|
-
v << "dir=#{direction_mapping.inspect}" unless direction_mapping.nil?
|
2214
|
-
v << "type=#{type_mapping}" unless type_mapping.nil?
|
2215
|
-
v << "nest=#{nest.inspect}" unless nest.nil?
|
2216
|
-
v << "simple=true" if @simple
|
2217
|
-
v << "protected=true" if @protected
|
2218
|
-
v << "prefix=#{@prefix.inspect}" unless @prefix.nil?
|
2219
|
-
v << "has-context" unless context.nil?
|
2220
|
-
v.join(" ") + "]"
|
2353
|
+
def prefix_colon
|
2354
|
+
@prefix_colon ||= "#{term}:".freeze
|
2355
|
+
end
|
2221
2356
|
end
|
2222
2357
|
end
|
2223
2358
|
end
|