json-ld 3.2.4 → 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 -771
- data/lib/json/ld/compact.rb +304 -304
- data/lib/json/ld/conneg.rb +179 -161
- data/lib/json/ld/context.rb +2080 -1913
- 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 +223 -204
- 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 -169
- 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 +55 -198
- 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 -2056
- 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 -2541
- data/spec/from_rdf_spec.rb +0 -1072
- 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 -1684
- data/spec/writer_spec.rb +0 -427
data/lib/json/ld/context.rb
CHANGED
@@ -1,2191 +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].context_base ||= context_canon.to_s
|
308
|
-
PRELOADED[context_canon.to_s]
|
309
|
-
else
|
310
|
-
# Load context document, if it is a string
|
311
|
-
Context.cache[context_canon.to_s] ||= begin
|
312
|
-
context_opts = @options.merge(
|
313
|
-
profile: 'http://www.w3.org/ns/json-ld#context',
|
314
|
-
requestProfile: 'http://www.w3.org/ns/json-ld#context',
|
315
|
-
base: nil)
|
316
|
-
#context_opts.delete(:headers)
|
317
|
-
JSON::LD::API.loadRemoteDocument(context.to_s, **context_opts) do |remote_doc|
|
318
|
-
# 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.
|
319
|
-
raise JsonLdError::InvalidRemoteContext, "#{context}" unless remote_doc.document.is_a?(Hash) && remote_doc.document.key?('@context')
|
320
|
-
|
321
|
-
# Parse stand-alone
|
322
|
-
ctx = Context.new(unfrozen: true, **options).dup
|
323
|
-
ctx.context_base = context.to_s
|
324
|
-
ctx = ctx.parse(remote_doc.document['@context'], remote_contexts: remote_contexts.dup)
|
325
|
-
ctx.context_base = context.to_s # In case it was altered
|
326
|
-
ctx.instance_variable_set(:@base, nil)
|
327
|
-
ctx
|
328
|
-
end
|
329
|
-
rescue JsonLdError::LoadingDocumentFailed => e
|
330
|
-
log_info("parse") {"Failed to retrieve @context from remote document at #{context_canon.inspect}: #{e.message}"}
|
331
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{context}: #{e.message}", e.backtrace
|
332
|
-
rescue JsonLdError
|
333
|
-
raise
|
334
|
-
rescue StandardError => e
|
335
|
-
log_info("parse") {"Failed to retrieve @context from remote document at #{context_canon.inspect}: #{e.message}"}
|
336
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{context}: #{e.message}", e.backtrace
|
337
|
-
end
|
338
|
-
end
|
303
|
+
remote_contexts << context.to_s
|
304
|
+
raise JsonLdError::ContextOverflow, context.to_s if remote_contexts.length >= MAX_CONTEXTS_LOADED
|
339
305
|
|
340
|
-
|
341
|
-
|
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}"}
|
342
309
|
|
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
|
-
|
378
|
-
context.
|
379
|
-
|
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
|
380
353
|
end
|
381
|
-
rescue JsonLdError::LoadingDocumentFailed => e
|
382
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{import_loc}: #{e.message}", e.backtrace
|
383
|
-
rescue JsonLdError
|
384
|
-
raise
|
385
|
-
rescue StandardError => e
|
386
|
-
raise JsonLdError::LoadingRemoteContextFailed, "#{import_loc}: #{e.message}", e.backtrace
|
387
354
|
end
|
388
|
-
else
|
389
|
-
result.send(setter, context[key], remote_contexts: remote_contexts)
|
390
|
-
end
|
391
|
-
context.delete(key)
|
392
|
-
end
|
393
355
|
|
394
|
-
|
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 = {}
|
395
431
|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
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
|
406
448
|
end
|
407
|
-
else
|
408
|
-
# 3.3) If context is not a JSON object, an invalid local context error has been detected and processing is aborted.
|
409
|
-
raise JsonLdError::InvalidLocalContext, "must be a URL, JSON object or array of same: #{context.inspect}"
|
410
449
|
end
|
450
|
+
result
|
411
451
|
end
|
412
|
-
end
|
413
|
-
result
|
414
|
-
end
|
415
452
|
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
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
|
434
472
|
end
|
435
473
|
end
|
436
|
-
end
|
437
474
|
|
438
|
-
|
439
|
-
|
440
|
-
|
475
|
+
# Add term definitions
|
476
|
+
context.term_definitions.each do |term, definition|
|
477
|
+
ctx.term_definitions[term] = definition
|
478
|
+
end
|
479
|
+
ctx
|
441
480
|
end
|
442
|
-
ctx
|
443
|
-
end
|
444
481
|
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
##
|
456
|
-
# Create Term Definition
|
457
|
-
#
|
458
|
-
# 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.
|
459
|
-
#
|
460
|
-
# 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.
|
461
|
-
#
|
462
|
-
# @param [Hash] local_context
|
463
|
-
# @param [String] term
|
464
|
-
# @param [Hash] defined
|
465
|
-
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
466
|
-
# @param [Boolean] protected if true, causes all terms to be marked protected
|
467
|
-
# @param [Boolean] override_protected Protected terms may be cleared.
|
468
|
-
# @param [Array<String>] remote_contexts
|
469
|
-
# @param [Boolean] validate_scoped (true).
|
470
|
-
# Validate scoped context, loading if necessary.
|
471
|
-
# If false, do not load scoped contexts.
|
472
|
-
# @raise [JsonLdError]
|
473
|
-
# Represents a cyclical term dependency
|
474
|
-
# @see https://www.w3.org/TR/json-ld11-api/index.html#create-term-definition
|
475
|
-
def create_term_definition(local_context, term, defined,
|
476
|
-
base: nil,
|
477
|
-
override_protected: false,
|
478
|
-
protected: nil,
|
479
|
-
remote_contexts: [],
|
480
|
-
validate_scoped: true)
|
481
|
-
# Expand a string value, unless it matches a keyword
|
482
|
-
# log_debug("create_term_definition") {"term = #{term.inspect}"}
|
483
|
-
|
484
|
-
# 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.
|
485
|
-
case defined[term]
|
486
|
-
when TrueClass then return
|
487
|
-
when nil
|
488
|
-
defined[term] = false
|
489
|
-
else
|
490
|
-
raise JsonLdError::CyclicIRIMapping, "Cyclical term dependency found: #{term.inspect}"
|
491
|
-
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
|
492
491
|
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
#
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
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
|
514
529
|
|
515
|
-
|
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
|
516
551
|
|
517
|
-
|
518
|
-
previous_definition = term_definitions[term]
|
519
|
-
if previous_definition && previous_definition.protected? && !override_protected
|
520
|
-
# Check later to detect identical redefinition
|
521
|
-
else
|
522
|
-
term_definitions.delete(term) if previous_definition
|
523
|
-
end
|
552
|
+
value = { '@id' => value } if simple_term
|
524
553
|
|
525
|
-
|
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
|
526
561
|
|
527
|
-
|
528
|
-
|
529
|
-
|
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
|
530
566
|
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
end
|
567
|
+
# log_debug("") {"Hash[#{term.inspect}] = #{value.inspect}"}
|
568
|
+
definition = TermDefinition.new(term)
|
569
|
+
definition.simple = simple_term
|
535
570
|
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
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
|
540
575
|
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
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
|
545
580
|
|
546
|
-
|
547
|
-
|
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
|
548
586
|
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
type
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
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
|
560
605
|
end
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
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
|
568
614
|
end
|
569
|
-
# log_debug("") {"type_mapping: #{type.inspect}"}
|
570
|
-
definition.type_mapping = type
|
571
|
-
end
|
572
615
|
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
raise JsonLdError::InvalidIRIMapping, "expected value of @reverse to be a string: #{value['@reverse'].inspect} on term #{term.inspect}" unless
|
577
|
-
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')
|
578
619
|
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
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
|
583
624
|
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
defined: defined)
|
589
|
-
raise JsonLdError::InvalidIRIMapping, "non-absolute @reverse IRI: #{definition.id} on term #{term.inspect}" unless
|
590
|
-
definition.id.is_a?(RDF::Node) || definition.id.is_a?(RDF::URI) && definition.id.absolute?
|
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
|
591
629
|
|
592
|
-
|
593
|
-
|
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
|
724
|
+
|
725
|
+
definition.id = vocab + term
|
726
|
+
# log_debug("") {"=> #{definition.id}"}
|
594
727
|
end
|
595
728
|
|
596
|
-
|
729
|
+
@iri_to_term[definition.id] = term if simple_term && definition.id
|
597
730
|
|
598
|
-
# 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.
|
599
731
|
if value.key?('@container')
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
definition.container_mapping
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
if !KEYWORDS.include?(value['@id'].to_s) && value['@id'].to_s.match?(/^@[a-zA-Z]+$/) && @options[:validate]
|
614
|
-
warn "Values beginning with '@' are reserved for future use and ignored: #{value['@id']}."
|
615
|
-
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
|
616
744
|
end
|
617
745
|
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
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
|
624
756
|
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
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}"
|
632
777
|
end
|
633
778
|
end
|
634
779
|
|
635
|
-
|
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
|
636
798
|
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
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
|
645
807
|
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
else
|
650
|
-
# Otherwise, term is an absolute IRI. Set the IRI mapping for definition to term
|
651
|
-
term
|
652
|
-
end
|
653
|
-
# log_debug("") {"=> #{definition.id}"}
|
654
|
-
elsif term.include?('/')
|
655
|
-
# If term is a relative IRI
|
656
|
-
definition.id = expand_iri(term, vocab: true)
|
657
|
-
raise JsonLdError::InvalidKeywordAlias, "expected term to expand to an absolute IRI #{term.inspect}" unless
|
658
|
-
definition.id.absolute?
|
659
|
-
elsif KEYWORDS.include?(term)
|
660
|
-
# This should only happen for @type when @container is @set
|
661
|
-
definition.id = term
|
662
|
-
else
|
663
|
-
# 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.
|
664
|
-
raise JsonLdError::InvalidIRIMapping, "relative term definition without vocab: #{term} on term #{term.inspect}" unless vocab
|
665
|
-
definition.id = vocab + term
|
666
|
-
# log_debug("") {"=> #{definition.id}"}
|
667
|
-
end
|
808
|
+
# log_debug("") {"direction_mapping: #{direction.inspect}"}
|
809
|
+
definition.direction_mapping = direction || false
|
810
|
+
end
|
668
811
|
|
669
|
-
|
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
|
822
|
+
|
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
|
670
832
|
|
671
|
-
|
672
|
-
|
673
|
-
|
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
|
674
839
|
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
definition.type_mapping ||= '@id'
|
679
|
-
# If definition includes @type with a value other than @id or @vocab, an illegal type mapping error has been detected
|
680
|
-
if !CONTEXT_TYPE_ID_VOCAB.include?(definition.type_mapping)
|
681
|
-
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"
|
682
843
|
end
|
683
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
|
684
853
|
end
|
685
854
|
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
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?
|
691
861
|
end
|
692
862
|
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
rescue JsonLdError => e
|
708
|
-
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
|
709
877
|
end
|
710
878
|
end
|
711
879
|
|
712
|
-
|
713
|
-
|
714
|
-
|
880
|
+
# @param [String] value
|
881
|
+
def default_language=(value, **options)
|
882
|
+
@default_language = case value
|
715
883
|
when String
|
716
884
|
# Warn on an invalid language tag, unless :validate is true, in which case it's an error
|
717
|
-
|
718
|
-
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}"
|
719
887
|
end
|
720
|
-
options[:lowercaseLanguage] ? value
|
888
|
+
options[:lowercaseLanguage] ? value.downcase : value
|
721
889
|
when nil
|
722
890
|
nil
|
723
891
|
else
|
724
|
-
raise JsonLdError::
|
892
|
+
raise JsonLdError::InvalidDefaultLanguage, "@language must be a string: #{value.inspect}"
|
725
893
|
end
|
726
|
-
# log_debug("") {"language_mapping: #{language.inspect}"}
|
727
|
-
definition.language_mapping = language || false
|
728
894
|
end
|
729
895
|
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
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
|
736
905
|
|
737
|
-
|
738
|
-
|
739
|
-
raise JsonLdError::InvalidNestValue, "nest must be a string, was #{nest.inspect}} on term #{term.inspect}" unless nest.is_a?(String)
|
740
|
-
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'
|
741
|
-
# log_debug("") {"nest: #{nest.inspect}"}
|
742
|
-
definition.nest = nest
|
906
|
+
value
|
907
|
+
end
|
743
908
|
end
|
744
909
|
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
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'
|
750
927
|
else
|
751
|
-
|
928
|
+
false
|
752
929
|
end
|
753
|
-
|
754
|
-
raise JsonLdError::InvalidTermDefinition, "keywords may not be used as prefixes" if pfx && KEYWORDS.include?(definition.id.to_s)
|
755
930
|
end
|
756
931
|
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
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
|
761
950
|
|
762
|
-
|
763
|
-
|
764
|
-
|
951
|
+
@processingMode = value
|
952
|
+
else
|
953
|
+
raise JsonLdError::InvalidVersionValue, value.inspect
|
954
|
+
end
|
955
|
+
end
|
765
956
|
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
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
|
773
971
|
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
# still might be relative to document
|
781
|
-
@base = value
|
782
|
-
else
|
783
|
-
@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
|
784
978
|
end
|
785
979
|
|
786
|
-
|
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
|
787
989
|
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
when String
|
792
|
-
# Warn on an invalid language tag, unless :validate is true, in which case it's an error
|
793
|
-
if value !~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/
|
794
|
-
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}"
|
795
993
|
end
|
796
|
-
options[:lowercaseLanguage] ? value.downcase : value
|
797
|
-
when nil
|
798
|
-
nil
|
799
|
-
else
|
800
|
-
raise JsonLdError::InvalidDefaultLanguage, "@language must be a string: #{value.inspect}"
|
801
|
-
end
|
802
|
-
end
|
803
994
|
|
804
|
-
# @param [String] value
|
805
|
-
def default_direction=(value, **options)
|
806
|
-
@default_direction = if value
|
807
|
-
raise JsonLdError::InvalidBaseDirection, "@direction must be one or 'ltr', or 'rtl': #{value.inspect}" unless %w(ltr rtl).include?(value)
|
808
995
|
value
|
809
|
-
else
|
810
|
-
nil
|
811
996
|
end
|
812
|
-
end
|
813
997
|
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
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
|
835
1038
|
|
836
|
-
|
837
|
-
|
838
|
-
#
|
839
|
-
# * 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"
|
840
|
-
#
|
841
|
-
# 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.
|
842
|
-
# If processingMode has been set, and it is not "json-ld-1.1", a "processing mode conflict" has been detecting, and processing is aborted.
|
843
|
-
#
|
844
|
-
# @param [String, Number] value
|
845
|
-
# @return [String]
|
846
|
-
# @raise [JsonLdError::ProcessingModeConflict]
|
847
|
-
def processingMode=(value = nil, **options)
|
848
|
-
value = "json-ld-1.1" if value == 1.1
|
849
|
-
case value
|
850
|
-
when "json-ld-1.0", "json-ld-1.1"
|
851
|
-
if @processingMode && @processingMode != value
|
852
|
-
raise JsonLdError::ProcessingModeConflict, "#{value} not compatible with #{@processingMode}"
|
853
|
-
end
|
854
|
-
@processingMode = value
|
855
|
-
else
|
856
|
-
raise JsonLdError::InvalidVersionValue, value.inspect
|
1039
|
+
# Return hash with @context, or empty
|
1040
|
+
use_context.nil? || use_context.empty? ? {} : { '@context' => use_context }
|
857
1041
|
end
|
858
|
-
end
|
859
1042
|
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
@
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
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
|
871
1076
|
end
|
872
|
-
expand_iri(value.to_s, vocab: true, documentRelative: true)
|
873
|
-
when nil
|
874
|
-
nil
|
875
|
-
else
|
876
|
-
raise JsonLdError::InvalidVocabMapping, "@vocab must be an IRI: #{value.inspect}"
|
877
|
-
end
|
878
|
-
end
|
879
1077
|
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
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)
|
889
1113
|
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
# If a context was supplied in global options, use that, otherwise, generate one
|
894
|
-
# from this representation.
|
895
|
-
#
|
896
|
-
# @param [Array, Hash, Context, IO, StringIO] provided_context (nil)
|
897
|
-
# Original context to use, if available
|
898
|
-
# @param [Hash{Symbol => Object}] options ({})
|
899
|
-
# @return [Hash]
|
900
|
-
def serialize(provided_context: nil, **options)
|
901
|
-
# log_debug("serlialize: generate context")
|
902
|
-
# log_debug("") {"=> context: #{inspect}"}
|
903
|
-
use_context = case provided_context
|
904
|
-
when String, RDF::URI
|
905
|
-
# log_debug "serlialize: reuse context: #{provided_context.inspect}"
|
906
|
-
provided_context.to_s
|
907
|
-
when Hash
|
908
|
-
# log_debug "serlialize: reuse context: #{provided_context.inspect}"
|
909
|
-
# If it has an @context entry use it, otherwise it is assumed to be the body of a context
|
910
|
-
provided_context.fetch('@context', provided_context)
|
911
|
-
when Array
|
912
|
-
# log_debug "serlialize: reuse context: #{provided_context.inspect}"
|
913
|
-
provided_context
|
914
|
-
when IO, StringIO
|
915
|
-
load_context(provided_context, **@options).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
|
-
nest_term = find_definition(term.nest)
|
1099
|
-
|
1100
|
-
|
1213
|
+
nest_term = find_definition(term.nest)
|
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
|
1218
|
+
|
1219
|
+
end
|
1220
|
+
term.nest
|
1221
|
+
end
|
1222
|
+
|
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)
|
1101
1234
|
end
|
1102
1235
|
end
|
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
1236
|
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
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))
|
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
|
1149
1249
|
end
|
1150
1250
|
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
# @param [String] value
|
1159
|
-
# A keyword, term, prefix:suffix or possibly relative IRI
|
1160
|
-
# @param [Boolean] as_string (false) transform RDF::Resource values to string
|
1161
|
-
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1162
|
-
# @param [Hash] defined
|
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)
|
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
|
1191
1258
|
end
|
1192
1259
|
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
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))
|
1275
|
+
end
|
1196
1276
|
|
1197
|
-
|
1198
|
-
|
1199
|
-
if (v_td = term_definitions[value]) && (vocab || KEYWORDS.include?(v_td.id))
|
1200
|
-
iri = base && v_td.id ? base.join(v_td.id) : v_td.id # vocab might be doc relative
|
1201
|
-
return (as_string ? iri.to_s : iri)
|
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 }
|
1202
1279
|
end
|
1203
1280
|
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
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'
|
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
|
1334
1459
|
end
|
1335
|
-
common_type ||= item_type
|
1336
|
-
if item_type != common_type
|
1337
|
-
common_type = '@none'
|
1338
|
-
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'
|
1385
|
-
|
1386
|
-
# In 1.1, an index map can be used to index values using @none, so add as a low priority
|
1387
|
-
containers.concat(CONTAINERS_INDEX_SET) unless index?(value)
|
1388
|
-
# Values without type or language can use @language map
|
1389
|
-
containers.concat(CONTAINERS_LANGUAGE) if value?(value) && value.keys == CONTAINERS_VALUE
|
1528
|
+
preferred_values << '@any'
|
1390
1529
|
|
1391
|
-
|
1392
|
-
|
1393
|
-
|
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
|
-
# 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.
|
1472
|
-
#
|
1473
|
-
# 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.
|
1474
|
-
#
|
1475
|
-
# @param [String] property
|
1476
|
-
# Associated property used to find coercion rules
|
1477
|
-
# @param [Hash, String] value
|
1478
|
-
# Value (literal or IRI) to be expanded
|
1479
|
-
# @param [Boolean] useNativeTypes (false) use native representations
|
1480
|
-
# @param [Boolean] rdfDirection (nil) decode i18n datatype if i18n-datatype
|
1481
|
-
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1482
|
-
# @param [Hash{Symbol => Object}] options
|
1483
|
-
#
|
1484
|
-
# @return [Hash] Object representation of value
|
1485
|
-
# @raise [RDF::ReaderError] if the iri cannot be expanded
|
1486
|
-
# @see https://www.w3.org/TR/json-ld11-api/#value-expansion
|
1487
|
-
def expand_value(property, value, useNativeTypes: false, rdfDirection: nil, base: nil, **options)
|
1488
|
-
td = term_definitions.fetch(property, TermDefinition.new(property))
|
1489
|
-
|
1490
|
-
# 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.
|
1491
|
-
if value.is_a?(String) && td.type_mapping == '@id'
|
1492
|
-
# log_debug("") {"as relative IRI: #{value.inspect}"}
|
1493
|
-
return {'@id' => expand_iri(value, documentRelative: true, base: base).to_s}
|
1590
|
+
iri
|
1494
1591
|
end
|
1495
1592
|
|
1496
|
-
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
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
|
1500
1618
|
|
1501
|
-
|
1502
|
-
|
1503
|
-
|
1504
|
-
|
1505
|
-
lit = RDF::Literal.new(value)
|
1506
|
-
{'@value' => lit.to_s, '@type' => lit.datatype.to_s}
|
1507
|
-
else
|
1508
|
-
# Otherwise, initialize result to a JSON object with an @value member whose value is set to value.
|
1509
|
-
res = {}
|
1510
|
-
|
1511
|
-
if td.type_mapping && !CONTAINERS_ID_VOCAB.include?(td.type_mapping.to_s)
|
1512
|
-
res['@type'] = td.type_mapping.to_s
|
1513
|
-
elsif value.is_a?(String)
|
1514
|
-
language = language(property)
|
1515
|
-
direction = direction(property)
|
1516
|
-
res['@language'] = language if language
|
1517
|
-
res['@direction'] = direction if direction
|
1518
|
-
end
|
1519
|
-
|
1520
|
-
res.merge('@value' => value)
|
1521
|
-
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
|
1522
1623
|
|
1523
|
-
|
1524
|
-
|
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
|
1525
1642
|
|
1526
|
-
|
1527
|
-
|
1528
|
-
#
|
1529
|
-
# @param [String] property
|
1530
|
-
# Associated property used to find coercion rules
|
1531
|
-
# @param [Hash] value
|
1532
|
-
# Value (literal or IRI), in full object representation, to be compacted
|
1533
|
-
# @param [String, RDF::URI] base for resolving document-relative IRIs
|
1534
|
-
#
|
1535
|
-
# @return [Hash] Object representation of value
|
1536
|
-
# @raise [JsonLdError] if the iri cannot be expanded
|
1537
|
-
# @see https://www.w3.org/TR/json-ld11-api/#value-compaction
|
1538
|
-
# FIXME: revisit the specification version of this.
|
1539
|
-
def compact_value(property, value, base: nil)
|
1540
|
-
# log_debug("compact_value") {"property: #{property.inspect}, value: #{value.inspect}"}
|
1541
|
-
|
1542
|
-
indexing = index?(value) && container(property).include?('@index')
|
1543
|
-
language = language(property)
|
1544
|
-
direction = direction(property)
|
1545
|
-
|
1546
|
-
result = case
|
1547
|
-
when coerce(property) == '@id' && value.key?('@id') && (value.keys - %w(@id @index)).empty?
|
1548
|
-
# Compact an @id coercion
|
1549
|
-
# log_debug("") {" (@id & coerce)"}
|
1550
|
-
compact_iri(value['@id'], base: base)
|
1551
|
-
when coerce(property) == '@vocab' && value.key?('@id') && (value.keys - %w(@id @index)).empty?
|
1552
|
-
# Compact an @id coercion
|
1553
|
-
# log_debug("") {" (@id & coerce & vocab)"}
|
1554
|
-
compact_iri(value['@id'], vocab: true)
|
1555
|
-
when value.key?('@id')
|
1556
|
-
# log_debug("") {" (@id)"}
|
1557
|
-
# return value as is
|
1558
|
-
value
|
1559
|
-
when value['@type'] && value['@type'] == coerce(property)
|
1560
|
-
# Compact common datatype
|
1561
|
-
# log_debug("") {" (@type & coerce) == #{coerce(property)}"}
|
1562
|
-
value['@value']
|
1563
|
-
when coerce(property) == '@none' || value['@type']
|
1564
|
-
# use original expanded value
|
1565
|
-
value
|
1566
|
-
when !value['@value'].is_a?(String)
|
1567
|
-
# log_debug("") {" (native)"}
|
1568
|
-
indexing || !index?(value) ? value['@value'] : value
|
1569
|
-
when value['@language'].to_s.downcase == language.to_s.downcase && value['@direction'] == direction
|
1570
|
-
# Compact language and direction
|
1571
|
-
indexing || !index?(value) ? value['@value'] : value
|
1572
|
-
else
|
1573
|
-
value
|
1643
|
+
res.merge('@value' => value)
|
1644
|
+
end
|
1574
1645
|
end
|
1575
1646
|
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
1579
|
-
|
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
|
1580
1692
|
else
|
1581
|
-
|
1693
|
+
value
|
1582
1694
|
end
|
1583
|
-
result = result.merge('@type' => c_type)
|
1584
|
-
end
|
1585
|
-
|
1586
|
-
# If the result is an object, tranform keys using any term keyword aliases
|
1587
|
-
if result.is_a?(Hash) && result.keys.any? {|k| self.alias(k) != k}
|
1588
|
-
# log_debug("") {" (map to key aliases)"}
|
1589
|
-
new_element = {}
|
1590
|
-
result.each do |k, v|
|
1591
|
-
new_element[self.alias(k)] = v
|
1592
|
-
end
|
1593
|
-
result = new_element
|
1594
|
-
end
|
1595
1695
|
|
1596
|
-
|
1597
|
-
|
1598
|
-
|
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
|
1599
1715
|
|
1600
|
-
|
1601
|
-
|
1602
|
-
|
1603
|
-
|
1604
|
-
|
1605
|
-
|
1606
|
-
|
1607
|
-
|
1608
|
-
|
1609
|
-
|
1610
|
-
|
1611
|
-
|
1612
|
-
|
1613
|
-
|
1614
|
-
"
|
1615
|
-
|
1616
|
-
|
1617
|
-
|
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 -*-
|
1618
1738
|
# frozen_string_literal: true
|
1619
1739
|
# This file generated automatically from #{context_base}
|
1620
1740
|
require 'json/ld'
|
1621
1741
|
class JSON::LD::Context
|
1622
1742
|
).gsub(/^ /, '') +
|
1623
|
-
|
1624
|
-
|
1625
|
-
|
1626
|
-
|
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
|
1627
1747
|
|
1628
|
-
|
1629
|
-
|
1630
|
-
|
1631
|
-
|
1632
|
-
|
1633
|
-
|
1634
|
-
|
1635
|
-
|
1636
|
-
|
1637
|
-
|
1638
|
-
|
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
|
1639
1759
|
|
1640
|
-
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
1646
|
-
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1650
|
-
|
1651
|
-
|
1652
|
-
|
1653
|
-
|
1654
|
-
|
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
|
1655
1777
|
end
|
1656
|
-
ec
|
1657
|
-
end
|
1658
1778
|
|
1659
|
-
|
1779
|
+
protected
|
1660
1780
|
|
1661
|
-
|
1662
|
-
|
1663
|
-
|
1664
|
-
|
1665
|
-
|
1666
|
-
|
1667
|
-
|
1668
|
-
|
1669
|
-
|
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
|
1670
1790
|
|
1671
|
-
|
1672
|
-
|
1673
|
-
|
1674
|
-
|
1675
|
-
|
1676
|
-
|
1677
|
-
|
1678
|
-
|
1679
|
-
|
1680
|
-
|
1681
|
-
|
1682
|
-
|
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
|
1683
1803
|
|
1684
|
-
|
1685
|
-
|
1686
|
-
CONTEXT_CONTAINER_ARRAY_TERMS = Set.new(%w(@set @list @graph)).freeze
|
1687
|
-
CONTEXT_CONTAINER_ID_GRAPH = Set.new(%w(@id @graph)).freeze
|
1688
|
-
CONTEXT_CONTAINER_INDEX_GRAPH = Set.new(%w(@index @graph)).freeze
|
1689
|
-
CONTEXT_BASE_FRAG_OR_QUERY = %w(? #).freeze
|
1690
|
-
CONTEXT_TYPE_ID_VOCAB = %w(@id @vocab).freeze
|
1691
|
-
|
1692
|
-
##
|
1693
|
-
# Reads the `@context` from an IO
|
1694
|
-
def load_context(io, **options)
|
1695
|
-
io.rewind
|
1696
|
-
remote_doc = API.loadRemoteDocument(io, **options)
|
1697
|
-
remote_doc.document.is_a?(String) ?
|
1698
|
-
MultiJson.load(remote_doc.document) :
|
1699
|
-
remote_doc.document
|
1700
|
-
end
|
1804
|
+
private
|
1701
1805
|
|
1702
|
-
|
1703
|
-
|
1704
|
-
|
1705
|
-
|
1706
|
-
|
1707
|
-
|
1708
|
-
|
1709
|
-
|
1710
|
-
|
1711
|
-
|
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
|
1712
1822
|
end
|
1713
|
-
end
|
1714
1823
|
|
1715
|
-
|
1716
|
-
|
1717
|
-
|
1718
|
-
|
1719
|
-
|
1720
|
-
|
1721
|
-
|
1722
|
-
|
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
|
1723
1889
|
|
1724
|
-
|
1725
|
-
|
1726
|
-
|
1727
|
-
|
1728
|
-
|
1729
|
-
|
1730
|
-
|
1731
|
-
|
1732
|
-
|
1733
|
-
|
1734
|
-
|
1735
|
-
|
1736
|
-
|
1737
|
-
|
1738
|
-
|
1739
|
-
|
1740
|
-
|
1741
|
-
|
1742
|
-
|
1743
|
-
|
1744
|
-
|
1745
|
-
|
1746
|
-
|
1747
|
-
|
1748
|
-
|
1749
|
-
|
1750
|
-
|
1751
|
-
|
1752
|
-
|
1753
|
-
|
1754
|
-
|
1755
|
-
|
1756
|
-
|
1757
|
-
|
1758
|
-
|
1759
|
-
a.length == b.length ? (a <=> b) : (a.length <=> b.length)
|
1760
|
-
end.each do |term|
|
1761
|
-
next unless td = term_definitions[term]
|
1762
|
-
|
1763
|
-
container = td.container_mapping.to_a.join('')
|
1764
|
-
if container.empty?
|
1765
|
-
container = td.as_set? ? %(@set) : %(@none)
|
1766
|
-
end
|
1767
|
-
|
1768
|
-
container_map = result[td.id.to_s] ||= {}
|
1769
|
-
tl_map = container_map[container] ||= {'@language' => {}, '@type' => {}, '@any' => {}}
|
1770
|
-
type_map = tl_map['@type']
|
1771
|
-
language_map = tl_map['@language']
|
1772
|
-
any_map = tl_map['@any']
|
1773
|
-
any_map['@none'] ||= term
|
1774
|
-
if td.reverse_property
|
1775
|
-
type_map['@reverse'] ||= term
|
1776
|
-
elsif td.type_mapping == '@none'
|
1777
|
-
type_map['@any'] ||= term
|
1778
|
-
language_map['@any'] ||= term
|
1779
|
-
any_map['@any'] ||= term
|
1780
|
-
elsif td.type_mapping
|
1781
|
-
type_map[td.type_mapping.to_s] ||= term
|
1782
|
-
elsif !td.language_mapping.nil? && !td.direction_mapping.nil?
|
1783
|
-
lang_dir = if td.language_mapping && td.direction_mapping
|
1784
|
-
"#{td.language_mapping}_#{td.direction_mapping}".downcase
|
1785
|
-
elsif td.language_mapping
|
1786
|
-
td.language_mapping.downcase
|
1787
|
-
elsif td.direction_mapping
|
1788
|
-
"_#{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
|
1789
1925
|
else
|
1790
|
-
|
1926
|
+
language_map[default_language] ||= term
|
1927
|
+
language_map['@none'] ||= term
|
1928
|
+
type_map['@none'] ||= term
|
1791
1929
|
end
|
1792
|
-
language_map[lang_dir] ||= term
|
1793
|
-
elsif !td.language_mapping.nil?
|
1794
|
-
lang_dir = (td.language_mapping || '@null').downcase
|
1795
|
-
language_map[lang_dir] ||= term
|
1796
|
-
elsif !td.direction_mapping.nil?
|
1797
|
-
lang_dir = td.direction_mapping ? "_#{td.direction_mapping}" : '@none'
|
1798
|
-
language_map[lang_dir] ||= term
|
1799
|
-
elsif default_direction
|
1800
|
-
language_map["_#{default_direction}"] ||= term
|
1801
|
-
language_map['@none'] ||= term
|
1802
|
-
type_map['@none'] ||= term
|
1803
|
-
else
|
1804
|
-
language_map[default_language] ||= term
|
1805
|
-
language_map['@none'] ||= term
|
1806
|
-
type_map['@none'] ||= term
|
1807
1930
|
end
|
1931
|
+
result
|
1808
1932
|
end
|
1809
|
-
result
|
1810
1933
|
end
|
1811
|
-
end
|
1812
1934
|
|
1813
|
-
|
1814
|
-
|
1815
|
-
|
1816
|
-
|
1817
|
-
|
1818
|
-
|
1819
|
-
|
1820
|
-
|
1821
|
-
|
1822
|
-
|
1823
|
-
|
1824
|
-
|
1825
|
-
|
1826
|
-
|
1827
|
-
|
1828
|
-
|
1829
|
-
|
1830
|
-
|
1831
|
-
|
1832
|
-
|
1833
|
-
|
1834
|
-
|
1835
|
-
|
1836
|
-
|
1837
|
-
|
1838
|
-
|
1839
|
-
|
1840
|
-
|
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
|
1841
1966
|
end
|
1967
|
+
# log_debug("=>") {"nil"}
|
1968
|
+
nil
|
1842
1969
|
end
|
1843
|
-
# log_debug("=>") {"nil"}
|
1844
|
-
nil
|
1845
|
-
end
|
1846
1970
|
|
1847
|
-
|
1848
|
-
|
1849
|
-
|
1850
|
-
|
1851
|
-
|
1852
|
-
|
1853
|
-
|
1854
|
-
|
1855
|
-
|
1856
|
-
|
1857
|
-
|
1858
|
-
|
1859
|
-
|
1860
|
-
|
1861
|
-
|
1862
|
-
|
1863
|
-
|
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])
|
1864
1989
|
|
1865
|
-
|
1866
|
-
|
1867
|
-
|
1868
|
-
|
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
|
1869
1997
|
end
|
1870
|
-
iri
|
1871
|
-
end
|
1872
1998
|
|
1873
|
-
|
1874
|
-
|
1875
|
-
|
1876
|
-
|
1877
|
-
|
1878
|
-
|
1879
|
-
|
1880
|
-
|
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
|
1881
2008
|
end
|
1882
2009
|
end
|
1883
|
-
end
|
1884
2010
|
|
1885
|
-
|
1886
|
-
|
1887
|
-
|
1888
|
-
|
1889
|
-
|
1890
|
-
|
1891
|
-
|
1892
|
-
|
1893
|
-
|
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
|
1894
2020
|
|
1895
|
-
|
1896
|
-
|
1897
|
-
|
1898
|
-
|
1899
|
-
|
1900
|
-
|
1901
|
-
|
1902
|
-
|
1903
|
-
|
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
|
1904
2031
|
end
|
1905
2032
|
end
|
1906
|
-
end
|
1907
2033
|
|
1908
|
-
|
1909
|
-
|
1910
|
-
|
1911
|
-
|
1912
|
-
|
1913
|
-
|
1914
|
-
|
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
|
1915
2041
|
|
1916
|
-
|
1917
|
-
|
1918
|
-
|
1919
|
-
|
1920
|
-
|
1921
|
-
|
1922
|
-
|
1923
|
-
|
1924
|
-
|
1925
|
-
|
1926
|
-
|
1927
|
-
|
1928
|
-
|
1929
|
-
|
1930
|
-
val.length == 1
|
1931
|
-
|
1932
|
-
|
1933
|
-
|
1934
|
-
|
1935
|
-
|
1936
|
-
|
1937
|
-
|
1938
|
-
|
1939
|
-
|
1940
|
-
|
1941
|
-
|
1942
|
-
"
|
1943
|
-
|
1944
|
-
|
1945
|
-
|
1946
|
-
|
1947
|
-
|
1948
|
-
elsif val.include?('@type') || val.include?('@graph')
|
1949
|
-
raise JsonLdError::InvalidContainerMapping,
|
1950
|
-
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" if
|
1951
|
-
processingMode('json-ld-1.0')
|
1952
|
-
raise JsonLdError::InvalidContainerMapping,
|
1953
|
-
"'@container' on term #{term.inspect} using @language cannot have any values other than @set, found #{container.inspect}" unless
|
1954
|
-
val.length == 1
|
1955
|
-
# Okay
|
1956
|
-
elsif val.empty?
|
1957
|
-
# Okay
|
1958
|
-
else
|
1959
|
-
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,
|
1960
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)
|
1961
2098
|
end
|
1962
|
-
Array(container)
|
1963
|
-
end
|
1964
2099
|
|
1965
|
-
|
1966
|
-
|
1967
|
-
|
1968
|
-
|
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
|
1969
2104
|
|
1970
|
-
|
1971
|
-
|
2105
|
+
# @return [String] term name
|
2106
|
+
attr_accessor :term
|
1972
2107
|
|
1973
|
-
|
1974
|
-
|
2108
|
+
# @return [String] Type mapping
|
2109
|
+
attr_accessor :type_mapping
|
1975
2110
|
|
1976
|
-
|
1977
|
-
|
1978
|
-
|
2111
|
+
# Base container mapping, without @set
|
2112
|
+
# @return [Array<'@index', '@language', '@index', '@set', '@type', '@id', '@graph'>] Container mapping
|
2113
|
+
attr_reader :container_mapping
|
1979
2114
|
|
1980
|
-
|
1981
|
-
|
2115
|
+
# @return [String] Term used for nest properties
|
2116
|
+
attr_accessor :nest
|
1982
2117
|
|
1983
|
-
|
1984
|
-
|
1985
|
-
|
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
|
1986
2121
|
|
1987
|
-
|
1988
|
-
|
1989
|
-
|
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
|
1990
2125
|
|
1991
|
-
|
1992
|
-
|
2126
|
+
# @return [Boolean] Reverse Property
|
2127
|
+
attr_accessor :reverse_property
|
1993
2128
|
|
1994
|
-
|
1995
|
-
|
1996
|
-
|
2129
|
+
# This is a simple term definition, not an expanded term definition
|
2130
|
+
# @return [Boolean]
|
2131
|
+
attr_accessor :simple
|
1997
2132
|
|
1998
|
-
|
1999
|
-
|
2000
|
-
|
2133
|
+
# Property used for data indexing; defaults to @index
|
2134
|
+
# @return [Boolean]
|
2135
|
+
attr_accessor :index
|
2001
2136
|
|
2002
|
-
|
2003
|
-
|
2137
|
+
# Indicate that term may be used as a prefix
|
2138
|
+
attr_writer :prefix
|
2004
2139
|
|
2005
|
-
|
2006
|
-
|
2007
|
-
|
2140
|
+
# Term-specific context
|
2141
|
+
# @return [Hash{String => Object}]
|
2142
|
+
attr_accessor :context
|
2008
2143
|
|
2009
|
-
|
2010
|
-
|
2011
|
-
|
2144
|
+
# Term is protected.
|
2145
|
+
# @return [Boolean]
|
2146
|
+
attr_writer :protected
|
2012
2147
|
|
2013
|
-
|
2014
|
-
|
2015
|
-
|
2148
|
+
# This is a simple term definition, not an expanded term definition
|
2149
|
+
# @return [Boolean] simple
|
2150
|
+
def simple?
|
2151
|
+
simple
|
2152
|
+
end
|
2016
2153
|
|
2017
|
-
|
2018
|
-
|
2019
|
-
|
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
|
2020
2159
|
|
2021
|
-
|
2022
|
-
|
2023
|
-
|
2024
|
-
|
2025
|
-
|
2026
|
-
|
2027
|
-
|
2028
|
-
|
2029
|
-
|
2030
|
-
|
2031
|
-
|
2032
|
-
|
2033
|
-
|
2034
|
-
|
2035
|
-
|
2036
|
-
|
2037
|
-
|
2038
|
-
|
2039
|
-
|
2040
|
-
|
2041
|
-
|
2042
|
-
|
2043
|
-
|
2044
|
-
|
2045
|
-
|
2046
|
-
|
2047
|
-
|
2048
|
-
|
2049
|
-
|
2050
|
-
|
2051
|
-
|
2052
|
-
|
2053
|
-
|
2054
|
-
|
2055
|
-
|
2056
|
-
|
2057
|
-
|
2058
|
-
|
2059
|
-
|
2060
|
-
|
2061
|
-
|
2062
|
-
|
2063
|
-
|
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
|
2064
2203
|
|
2065
|
-
|
2066
|
-
|
2067
|
-
|
2068
|
-
|
2069
|
-
# Set container mapping, from an array which may include @set
|
2070
|
-
def container_mapping=(mapping)
|
2071
|
-
mapping = case mapping
|
2072
|
-
when Set then mapping
|
2073
|
-
when Array then Set.new(mapping)
|
2074
|
-
when String then Set[mapping]
|
2075
|
-
when nil then Set.new
|
2076
|
-
else
|
2077
|
-
raise "Shouldn't happen with #{mapping.inspect}"
|
2204
|
+
# Term is protected.
|
2205
|
+
# @return [Boolean]
|
2206
|
+
def protected?
|
2207
|
+
!!@protected
|
2078
2208
|
end
|
2079
|
-
|
2080
|
-
|
2081
|
-
|
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)
|
2082
2216
|
end
|
2083
|
-
@container_mapping = mapping
|
2084
|
-
@index ||= '@index' if mapping.include?('@index')
|
2085
|
-
end
|
2086
2217
|
|
2087
|
-
|
2088
|
-
|
2089
|
-
|
2090
|
-
|
2091
|
-
|
2092
|
-
|
2093
|
-
cid = if context.vocab && id.start_with?(context.vocab)
|
2094
|
-
# Nothing to return unless it's the same as the vocab
|
2095
|
-
id == context.vocab ? context.vocab : id.to_s[context.vocab.length..-1]
|
2096
|
-
else
|
2097
|
-
# Find a term to act as a prefix
|
2098
|
-
iri, prefix = context.iri_to_term.detect {|i,p| id.to_s.start_with?(i.to_s)}
|
2099
|
-
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)
|
2100
2224
|
end
|
2101
2225
|
|
2102
|
-
|
2103
|
-
|
2104
|
-
|
2105
|
-
|
2106
|
-
|
2107
|
-
|
2108
|
-
|
2109
|
-
|
2110
|
-
|
2111
|
-
|
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
|
2112
2301
|
end
|
2302
|
+
defn << "#{acc}: #{v.inspect}" if v
|
2113
2303
|
end
|
2304
|
+
defn.join(', ') + ")"
|
2305
|
+
end
|
2114
2306
|
|
2115
|
-
|
2116
|
-
|
2117
|
-
|
2118
|
-
|
2119
|
-
# Language set as false to be output as null
|
2120
|
-
defn['@language'] = (@language_mapping ? @language_mapping : nil) unless @language_mapping.nil?
|
2121
|
-
defn['@direction'] = (@direction_mapping ? @direction_mapping : nil) unless @direction_mapping.nil?
|
2122
|
-
defn['@context'] = @context if @context
|
2123
|
-
defn['@nest'] = @nest if @nest
|
2124
|
-
defn['@index'] = @index if @index
|
2125
|
-
defn['@prefix'] = @prefix unless @prefix.nil?
|
2126
|
-
defn
|
2307
|
+
# If container mapping was defined along with @set
|
2308
|
+
# @return [Boolean]
|
2309
|
+
def as_set?
|
2310
|
+
@as_set || false
|
2127
2311
|
end
|
2128
|
-
end
|
2129
2312
|
|
2130
|
-
|
2131
|
-
|
2132
|
-
|
2133
|
-
|
2134
|
-
|
2135
|
-
|
2136
|
-
|
2137
|
-
|
2138
|
-
|
2139
|
-
|
2140
|
-
|
2141
|
-
|
2142
|
-
|
2143
|
-
|
2144
|
-
|
2145
|
-
|
2146
|
-
|
2147
|
-
|
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
|
2148
2331
|
|
2149
|
-
|
2150
|
-
|
2151
|
-
|
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
|
2152
2350
|
|
2153
|
-
|
2154
|
-
# @return [Boolean]
|
2155
|
-
def ==(other)
|
2156
|
-
other.is_a?(TermDefinition) &&
|
2157
|
-
id == other.id &&
|
2158
|
-
term == other.term &&
|
2159
|
-
type_mapping == other.type_mapping &&
|
2160
|
-
container_mapping == other.container_mapping &&
|
2161
|
-
nest == other.nest &&
|
2162
|
-
language_mapping == other.language_mapping &&
|
2163
|
-
direction_mapping == other.direction_mapping &&
|
2164
|
-
reverse_property == other.reverse_property &&
|
2165
|
-
simple == other.simple &&
|
2166
|
-
index == other.index &&
|
2167
|
-
context == other.context &&
|
2168
|
-
prefix? == other.prefix? &&
|
2169
|
-
as_set? == other.as_set?
|
2170
|
-
end
|
2351
|
+
private
|
2171
2352
|
|
2172
|
-
|
2173
|
-
|
2174
|
-
|
2175
|
-
v << "index=#{index.inspect}" unless index.nil?
|
2176
|
-
v << "term=#{@term}"
|
2177
|
-
v << "rev" if reverse_property
|
2178
|
-
v << "container=#{container_mapping}" if container_mapping
|
2179
|
-
v << "as_set=#{as_set?.inspect}"
|
2180
|
-
v << "lang=#{language_mapping.inspect}" unless language_mapping.nil?
|
2181
|
-
v << "dir=#{direction_mapping.inspect}" unless direction_mapping.nil?
|
2182
|
-
v << "type=#{type_mapping}" unless type_mapping.nil?
|
2183
|
-
v << "nest=#{nest.inspect}" unless nest.nil?
|
2184
|
-
v << "simple=true" if @simple
|
2185
|
-
v << "protected=true" if @protected
|
2186
|
-
v << "prefix=#{@prefix.inspect}" unless @prefix.nil?
|
2187
|
-
v << "has-context" unless context.nil?
|
2188
|
-
v.join(" ") + "]"
|
2353
|
+
def prefix_colon
|
2354
|
+
@prefix_colon ||= "#{term}:".freeze
|
2355
|
+
end
|
2189
2356
|
end
|
2190
2357
|
end
|
2191
2358
|
end
|