json-ld 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.markdown +15 -0
- data/README.markdown +199 -3
- data/VERSION +1 -1
- data/lib/json/ld.rb +44 -4
- data/lib/json/ld/api.rb +220 -224
- data/lib/json/ld/compact.rb +126 -0
- data/lib/json/ld/evaluation_context.rb +428 -204
- data/lib/json/ld/expand.rb +185 -0
- data/lib/json/ld/extensions.rb +34 -7
- data/lib/json/ld/format.rb +2 -17
- data/lib/json/ld/frame.rb +452 -0
- data/lib/json/ld/from_rdf.rb +166 -0
- data/lib/json/ld/reader.rb +7 -231
- data/lib/json/ld/to_rdf.rb +181 -0
- data/lib/json/ld/utils.rb +97 -0
- data/lib/json/ld/writer.rb +33 -471
- metadata +51 -34
- data/lib/json/ld/normalize.rb +0 -120
@@ -0,0 +1,126 @@
|
|
1
|
+
module JSON::LD
|
2
|
+
module Compact
|
3
|
+
include Utils
|
4
|
+
|
5
|
+
##
|
6
|
+
# Compact an expanded Array or Hash given an active property and a context.
|
7
|
+
#
|
8
|
+
# @param [Array, Hash] element
|
9
|
+
# @param [String] property (nil)
|
10
|
+
# @param [EvaluationContext] context
|
11
|
+
# @return [Array, Hash]
|
12
|
+
def compact(element, property = nil)
|
13
|
+
if property.nil?
|
14
|
+
debug("compact") {"element: #{element.inspect}, ec: #{context.inspect}"}
|
15
|
+
else
|
16
|
+
debug("compact") {"property: #{property.inspect}"}
|
17
|
+
end
|
18
|
+
case element
|
19
|
+
when Array
|
20
|
+
# 1) If value is an array, process each item in value recursively using
|
21
|
+
# this algorithm, passing copies of the active context and the
|
22
|
+
# active property.
|
23
|
+
debug("compact") {"Array #{element.inspect}"}
|
24
|
+
result = depth {element.map {|v| compact(v, property)}}
|
25
|
+
|
26
|
+
# If element has a single member and the active property has no
|
27
|
+
# @container mapping to @list or @set, the compacted value is that
|
28
|
+
# member; otherwise the compacted value is element
|
29
|
+
if result.length == 1
|
30
|
+
debug("=> extract single element: #{result.first.inspect}")
|
31
|
+
result.first
|
32
|
+
else
|
33
|
+
debug("=> array result: #{result.inspect}")
|
34
|
+
result
|
35
|
+
end
|
36
|
+
when Hash
|
37
|
+
# 2) Otherwise, if element is an object:
|
38
|
+
result = {}
|
39
|
+
|
40
|
+
if k = %w(@list @set @value).detect {|container| element.has_key?(container)}
|
41
|
+
debug("compact") {"#{k}: container(#{property}) = #{context.container(property)}"}
|
42
|
+
end
|
43
|
+
|
44
|
+
k ||= '@id' if element.keys == ['@id']
|
45
|
+
|
46
|
+
case k
|
47
|
+
when '@value', '@id'
|
48
|
+
# If element has an @value property or element is a subject reference, return the result of performing
|
49
|
+
# Value Compaction on element using active property.
|
50
|
+
v = context.compact_value(property, element, :depth => @depth)
|
51
|
+
debug("compact") {"value optimization, return as #{v.inspect}"}
|
52
|
+
return v
|
53
|
+
when '@list'
|
54
|
+
# Otherwise, if the active property has a @container mapping to @list and element has a corresponding @list property, recursively compact that property's value passing a copy of the active context and the active property ensuring that the result is an array and removing null values.
|
55
|
+
compacted_key = context.compact_iri(k, :position => :predicate, :depth => @depth)
|
56
|
+
v = depth { compact(element[k], property) }
|
57
|
+
|
58
|
+
# Return either the result as an array, as an object with a key of @list (or appropriate alias from active context
|
59
|
+
v = [v].compact unless v.is_a?(Array)
|
60
|
+
v = {compacted_key => v} unless context.container(property) == k
|
61
|
+
debug("compact") {"@list result, return as #{v.inspect}"}
|
62
|
+
return v
|
63
|
+
end
|
64
|
+
|
65
|
+
# Otherwise, for each property and value in element:
|
66
|
+
element.each do |key, value|
|
67
|
+
debug("compact") {"#{key}: #{value.inspect}"}
|
68
|
+
|
69
|
+
if %(@id @type).include?(key)
|
70
|
+
compacted_key = context.compact_iri(key, :position => :predicate, :depth => @depth)
|
71
|
+
result[compacted_key] = case value
|
72
|
+
when String
|
73
|
+
# If value is a string, the compacted value is the result of performing IRI Compaction on value.
|
74
|
+
debug {" => compacted string for #{key}"}
|
75
|
+
context.compact_iri(value, :position => :subject, :depth => @depth)
|
76
|
+
when Array
|
77
|
+
# Otherwise, value must be an array. Perform IRI Compaction on every entry of value. If value contains just one entry, value is set to that entry
|
78
|
+
compacted_value = value.map {|v| context.compact_iri(v, :position => :subject, :depth => @depth)}
|
79
|
+
debug {" => compacted value(#{key}): #{compacted_value.inspect}"}
|
80
|
+
compacted_value = compacted_value.first if compacted_value.length == 1
|
81
|
+
compacted_value
|
82
|
+
end
|
83
|
+
else
|
84
|
+
if value.empty?
|
85
|
+
# Make sure that an empty array is preserved
|
86
|
+
compacted_key = context.compact_iri(key, :position => :predicate, :depth => @depth)
|
87
|
+
result[compacted_key] = value
|
88
|
+
end
|
89
|
+
|
90
|
+
# For each item in value:
|
91
|
+
raise ProcessingError, "found #{value.inspect} for #{key} if #{element.inspect}" unless value.is_a?(Array)
|
92
|
+
value.each do |item|
|
93
|
+
compacted_key = context.compact_iri(key, :position => :predicate, :value => item, :depth => @depth)
|
94
|
+
debug {" => compacted key: #{compacted_key.inspect} for #{item.inspect}"}
|
95
|
+
|
96
|
+
compacted_item = depth {self.compact(item, compacted_key)}
|
97
|
+
debug {" => compacted value: #{compacted_value.inspect}"}
|
98
|
+
|
99
|
+
case result[compacted_key]
|
100
|
+
when Array
|
101
|
+
result[compacted_key] << compacted_item
|
102
|
+
when nil
|
103
|
+
if !compacted_value.is_a?(Array) && context.container(compacted_key) == '@set'
|
104
|
+
compacted_item = [compacted_item].compact
|
105
|
+
debug {" => as @set: #{compacted_item.inspect}"}
|
106
|
+
end
|
107
|
+
result[compacted_key] = compacted_item
|
108
|
+
else
|
109
|
+
result[compacted_key] = [result[compacted_key], compacted_item]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Re-order result keys
|
116
|
+
r = Hash.ordered
|
117
|
+
result.keys.sort.each {|k| r[k] = result[k]}
|
118
|
+
r
|
119
|
+
else
|
120
|
+
# For other types, the compacted value is the element value
|
121
|
+
debug("compact") {element.class.to_s}
|
122
|
+
element
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -4,6 +4,8 @@ require 'bigdecimal'
|
|
4
4
|
|
5
5
|
module JSON::LD
|
6
6
|
class EvaluationContext # :nodoc:
|
7
|
+
include Utils
|
8
|
+
|
7
9
|
# The base.
|
8
10
|
#
|
9
11
|
# The document base IRI, used for expanding relative IRIs.
|
@@ -39,17 +41,30 @@ module JSON::LD
|
|
39
41
|
|
40
42
|
# List coercion
|
41
43
|
#
|
42
|
-
# The @
|
43
|
-
#
|
44
|
-
# @
|
45
|
-
|
44
|
+
# The @container keyword is used to specify how arrays are to be treated.
|
45
|
+
# A value of @list indicates that arrays of values are to be treated as an ordered list.
|
46
|
+
# A value of @set indicates that arrays are to be treated as unordered and that
|
47
|
+
# singular values are always coerced to an array form on expansion and compaction.
|
48
|
+
# @attr [Hash{String => String}]
|
49
|
+
attr :containers, true
|
50
|
+
|
51
|
+
# Language coercion
|
52
|
+
#
|
53
|
+
# The @language keyword is used to specify language coercion rules for the data. For each key in the map, the
|
54
|
+
# key is a String representation of the property for which String values will be coerced and
|
55
|
+
# the value is the language to coerce to. If no property-specific language is given,
|
56
|
+
# any default language from the context is used.
|
57
|
+
#
|
58
|
+
# @attr [Hash{String => String}]
|
59
|
+
attr :languages, true
|
46
60
|
|
47
61
|
# Default language
|
48
62
|
#
|
63
|
+
#
|
49
64
|
# This adds a language to plain strings that aren't otherwise coerced
|
50
65
|
# @attr [String]
|
51
|
-
attr :
|
52
|
-
|
66
|
+
attr :default_language, true
|
67
|
+
|
53
68
|
# Global options used in generating IRIs
|
54
69
|
# @attr [Hash] options
|
55
70
|
attr :options, true
|
@@ -63,10 +78,11 @@ module JSON::LD
|
|
63
78
|
# @yieldparam [EvaluationContext]
|
64
79
|
# @return [EvaluationContext]
|
65
80
|
def initialize(options = {})
|
66
|
-
@base = RDF::URI(options[:
|
81
|
+
@base = RDF::URI(options[:base]) if options[:base]
|
67
82
|
@mappings = {}
|
68
83
|
@coercions = {}
|
69
|
-
@
|
84
|
+
@containers = {}
|
85
|
+
@languages = {}
|
70
86
|
@iri_to_curie = {}
|
71
87
|
@iri_to_term = {
|
72
88
|
RDF.to_uri.to_s => "rdf",
|
@@ -77,7 +93,7 @@ module JSON::LD
|
|
77
93
|
|
78
94
|
# Load any defined prefixes
|
79
95
|
(options[:prefixes] || {}).each_pair do |k, v|
|
80
|
-
@iri_to_term[v.to_s] = k
|
96
|
+
@iri_to_term[v.to_s] = k unless k.nil?
|
81
97
|
end
|
82
98
|
|
83
99
|
debug("init") {"iri_to_term: #{iri_to_term.inspect}"}
|
@@ -87,12 +103,14 @@ module JSON::LD
|
|
87
103
|
|
88
104
|
# Create an Evaluation Context using an existing context as a start by parsing the input.
|
89
105
|
#
|
90
|
-
# @param [
|
106
|
+
# @param [String, #read, Array, Hash, EvaluatoinContext] input
|
91
107
|
# @return [EvaluationContext] context
|
92
108
|
# @raise [InvalidContext]
|
93
109
|
# on a remote context load error, syntax error, or a reference to a term which is not defined.
|
94
110
|
def parse(context)
|
95
111
|
case context
|
112
|
+
when nil
|
113
|
+
EvaluationContext.new
|
96
114
|
when EvaluationContext
|
97
115
|
debug("parse") {"context: #{context.inspect}"}
|
98
116
|
context.dup
|
@@ -113,7 +131,7 @@ module JSON::LD
|
|
113
131
|
# Load context document, if it is a string
|
114
132
|
ec = nil
|
115
133
|
begin
|
116
|
-
|
134
|
+
RDF::Util::File.open_file(context.to_s) {|f| ec = parse(f)}
|
117
135
|
ec.provided_context = context
|
118
136
|
debug("parse") {"=> provided_context: #{context.inspect}"}
|
119
137
|
ec
|
@@ -134,33 +152,36 @@ module JSON::LD
|
|
134
152
|
when Hash
|
135
153
|
new_ec = self.dup
|
136
154
|
new_ec.provided_context = context
|
137
|
-
debug("parse") {"=> provided_context: #{context.inspect}"}
|
138
155
|
|
139
156
|
num_updates = 1
|
140
157
|
while num_updates > 0 do
|
141
158
|
num_updates = 0
|
142
159
|
|
143
|
-
# Map terms to IRIs first
|
160
|
+
# Map terms to IRIs/keywords first
|
144
161
|
context.each do |key, value|
|
145
162
|
# Expand a string value, unless it matches a keyword
|
146
163
|
debug("parse") {"Hash[#{key}] = #{value.inspect}"}
|
147
|
-
if
|
148
|
-
new_ec.
|
164
|
+
if key == '@language'
|
165
|
+
new_ec.default_language = value
|
149
166
|
elsif term_valid?(key)
|
167
|
+
# Remove all coercion information for the property
|
168
|
+
new_ec.set_coerce(key, nil)
|
169
|
+
new_ec.set_container(key, nil)
|
170
|
+
@languages.delete(key)
|
171
|
+
|
150
172
|
# Extract IRI mapping. This is complicated, as @id may have been aliased
|
151
|
-
if value.is_a?(Hash)
|
152
|
-
id_key = value.keys.detect {|k| new_ec.mapping(k) == '@id'} || '@id'
|
153
|
-
value = value[id_key]
|
154
|
-
end
|
173
|
+
value = value.fetch('@id', nil) if value.is_a?(Hash)
|
155
174
|
raise InvalidContext::Syntax, "unknown mapping for #{key.inspect} to #{value.class}" unless value.is_a?(String) || value.nil?
|
156
175
|
|
157
176
|
iri = new_ec.expand_iri(value, :position => :predicate) if value.is_a?(String)
|
158
|
-
if iri && new_ec.mappings
|
177
|
+
if iri && new_ec.mappings.fetch(key, nil) != iri
|
159
178
|
# Record term definition
|
160
|
-
new_ec.
|
179
|
+
new_ec.set_mapping(key, iri)
|
161
180
|
num_updates += 1
|
181
|
+
elsif value.nil?
|
182
|
+
new_ec.set_mapping(key, nil)
|
162
183
|
end
|
163
|
-
|
184
|
+
else
|
164
185
|
raise InvalidContext::Syntax, "key #{key.inspect} is invalid"
|
165
186
|
end
|
166
187
|
end
|
@@ -170,37 +191,44 @@ module JSON::LD
|
|
170
191
|
context.each do |key, value|
|
171
192
|
# Expand a string value, unless it matches a keyword
|
172
193
|
debug("parse") {"coercion/list: Hash[#{key}] = #{value.inspect}"}
|
173
|
-
prop = new_ec.expand_iri(key, :position => :predicate).to_s
|
174
194
|
case value
|
175
195
|
when Hash
|
176
|
-
# Must have one of @id, @type or @
|
177
|
-
|
178
|
-
raise InvalidContext::Syntax, "mapping for #{key.inspect} missing one of @id, @type or @list" if (%w(@id @type @list) & expanded_keys).empty?
|
179
|
-
raise InvalidContext::Syntax, "unknown mappings for #{key.inspect}: #{value.keys.inspect}" unless (expanded_keys - %w(@id @type @list)).empty?
|
196
|
+
# Must have one of @id, @language, @type or @container
|
197
|
+
raise InvalidContext::Syntax, "mapping for #{key.inspect} missing one of @id, @language, @type or @container" if (%w(@id @language @type @container) & value.keys).empty?
|
180
198
|
value.each do |key2, value2|
|
181
|
-
expanded_key = new_ec.mapping(key2) || key2
|
182
199
|
iri = new_ec.expand_iri(value2, :position => :predicate) if value2.is_a?(String)
|
183
|
-
case
|
200
|
+
case key2
|
184
201
|
when '@type'
|
185
202
|
raise InvalidContext::Syntax, "unknown mapping for '@type' to #{value2.class}" unless value2.is_a?(String) || value2.nil?
|
186
|
-
if new_ec.coerce(
|
203
|
+
if new_ec.coerce(key) != iri
|
187
204
|
raise InvalidContext::Syntax, "unknown mapping for '@type' to #{iri.inspect}" unless RDF::URI(iri).absolute? || iri == '@id'
|
188
205
|
# Record term coercion
|
189
|
-
|
190
|
-
|
206
|
+
new_ec.set_coerce(key, iri)
|
207
|
+
end
|
208
|
+
when '@container'
|
209
|
+
raise InvalidContext::Syntax, "unknown mapping for '@container' to #{value2.class}" unless %w(@list @set).include?(value2)
|
210
|
+
if new_ec.container(key) != value2
|
211
|
+
debug("parse") {"container #{key.inspect} as #{value2.inspect}"}
|
212
|
+
new_ec.set_container(key, value2)
|
191
213
|
end
|
192
|
-
when '@
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
new_ec.list(prop, value2)
|
214
|
+
when '@language'
|
215
|
+
if !new_ec.languages.has_key?(key) || new_ec.languages[key] != value2
|
216
|
+
debug("parse") {"language #{key.inspect} as #{value2.inspect}"}
|
217
|
+
new_ec.set_language(key, value2)
|
197
218
|
end
|
198
219
|
end
|
199
220
|
end
|
200
|
-
|
221
|
+
|
222
|
+
# If value has no @id, create a mapping from key
|
223
|
+
# to the expanded key IRI
|
224
|
+
unless value.has_key?('@id')
|
225
|
+
iri = new_ec.expand_iri(key, :position => :predicate)
|
226
|
+
new_ec.set_mapping(key, iri)
|
227
|
+
end
|
228
|
+
when nil, String
|
201
229
|
# handled in previous loop
|
202
230
|
else
|
203
|
-
raise InvalidContext::Syntax, "
|
231
|
+
raise InvalidContext::Syntax, "attempt to map #{key.inspect} to #{value.class}"
|
204
232
|
end
|
205
233
|
end
|
206
234
|
|
@@ -224,50 +252,52 @@ module JSON::LD
|
|
224
252
|
else
|
225
253
|
debug("serlialize: generate context")
|
226
254
|
debug {"=> context: #{inspect}"}
|
227
|
-
ctx = Hash.
|
228
|
-
ctx['@language'] =
|
255
|
+
ctx = Hash.ordered
|
256
|
+
ctx['@language'] = default_language.to_s if default_language
|
229
257
|
|
230
|
-
#
|
231
|
-
mappings.keys.sort
|
258
|
+
# Mappings
|
259
|
+
mappings.keys.sort{|a, b| a.to_s <=> b.to_s}.each do |k|
|
232
260
|
next unless term_valid?(k.to_s)
|
233
261
|
debug {"=> mappings[#{k}] => #{mappings[k]}"}
|
234
|
-
ctx[k
|
262
|
+
ctx[k] = mappings[k].to_s
|
235
263
|
end
|
236
264
|
|
237
|
-
unless coercions.empty? &&
|
265
|
+
unless coercions.empty? && containers.empty? && languages.empty?
|
238
266
|
# Coerce
|
239
|
-
(coercions.keys +
|
240
|
-
next if
|
241
|
-
|
242
|
-
k_iri = compact_iri(k, :position => :predicate, :depth => @depth).to_s
|
243
|
-
k_prefix = k_iri.split(':').first
|
267
|
+
(coercions.keys + containers.keys + languages.keys).uniq.sort.each do |k|
|
268
|
+
next if k == '@type'
|
244
269
|
|
245
270
|
# Turn into long form
|
246
|
-
ctx[
|
247
|
-
if ctx[
|
248
|
-
defn = Hash.
|
249
|
-
defn[
|
250
|
-
ctx[
|
271
|
+
ctx[k] ||= Hash.ordered
|
272
|
+
if ctx[k].is_a?(String)
|
273
|
+
defn = Hash.ordered
|
274
|
+
defn["@id"] = compact_iri(ctx[k], :position => :subject, :not_term => true)
|
275
|
+
ctx[k] = defn
|
251
276
|
end
|
252
277
|
|
253
278
|
debug {"=> coerce(#{k}) => #{coerce(k)}"}
|
254
279
|
if coerce(k) && !NATIVE_DATATYPES.include?(coerce(k))
|
255
|
-
|
256
|
-
dt = compact_iri(
|
280
|
+
dt = coerce(k)
|
281
|
+
dt = compact_iri(dt, :position => :datatype) unless dt == '@id'
|
257
282
|
# Fold into existing definition
|
258
|
-
ctx[
|
259
|
-
debug {"=>
|
283
|
+
ctx[k]["@type"] = dt
|
284
|
+
debug {"=> datatype[#{k}] => #{dt}"}
|
285
|
+
end
|
286
|
+
|
287
|
+
debug {"=> container(#{k}) => #{container(k)}"}
|
288
|
+
if %w(@list @set).include?(container(k))
|
289
|
+
ctx[k]["@container"] = container(k)
|
290
|
+
debug {"=> container[#{k}] => #{container(k).inspect}"}
|
260
291
|
end
|
261
292
|
|
262
|
-
debug {"=>
|
263
|
-
if
|
264
|
-
|
265
|
-
|
266
|
-
debug {"=> reuse list_range[#{k_iri}] => true"}
|
293
|
+
debug {"=> language(#{k}) => #{language(k)}"}
|
294
|
+
if language(k) != default_language
|
295
|
+
ctx[k]["@language"] = language(k) ? language(k) : nil
|
296
|
+
debug {"=> language[#{k}] => #{language(k).inspect}"}
|
267
297
|
end
|
268
298
|
|
269
299
|
# Remove an empty definition
|
270
|
-
ctx.delete(
|
300
|
+
ctx.delete(k) if ctx[k].empty?
|
271
301
|
end
|
272
302
|
end
|
273
303
|
|
@@ -276,30 +306,46 @@ module JSON::LD
|
|
276
306
|
end
|
277
307
|
|
278
308
|
# Return hash with @context, or empty
|
279
|
-
r = Hash.
|
309
|
+
r = Hash.ordered
|
280
310
|
r['@context'] = use_context unless use_context.nil? || use_context.empty?
|
281
311
|
r
|
282
312
|
end
|
283
313
|
end
|
284
314
|
|
285
315
|
##
|
286
|
-
# Retrieve term mapping
|
316
|
+
# Retrieve term mapping
|
287
317
|
#
|
288
318
|
# @param [String, #to_s] term
|
289
|
-
# @param [RDF::URI, String] value (nil)
|
290
319
|
#
|
291
320
|
# @return [RDF::URI, String]
|
292
|
-
def mapping(term
|
321
|
+
def mapping(term)
|
322
|
+
@mappings.fetch(term.to_s, nil)
|
323
|
+
end
|
324
|
+
|
325
|
+
##
|
326
|
+
# Set term mapping
|
327
|
+
#
|
328
|
+
# @param [String] term
|
329
|
+
# @param [RDF::URI, String] value
|
330
|
+
#
|
331
|
+
# @return [RDF::URI, String]
|
332
|
+
def set_mapping(term, value)
|
333
|
+
# raise InvalidContext::Syntax, "mapping term #{term.inspect} must be a string" unless term.is_a?(String)
|
334
|
+
# raise InvalidContext::Syntax, "mapping value #{value.inspect} must be an RDF::URI" unless value.nil? || value.to_s[0,1] == '@' || value.is_a?(RDF::URI)
|
335
|
+
debug {"map #{term.inspect} to #{value}"} unless @mappings[term] == value
|
336
|
+
iri_to_term.delete(@mappings[term].to_s) if @mappings[term]
|
293
337
|
if value
|
294
|
-
|
295
|
-
@
|
338
|
+
@mappings[term] = value
|
339
|
+
@options[:prefixes][term] = value if @options.has_key?(:prefixes)
|
296
340
|
iri_to_term[value.to_s] = term
|
341
|
+
else
|
342
|
+
@mappings.delete(term)
|
343
|
+
nil
|
297
344
|
end
|
298
|
-
@mappings.has_key?(term.to_s) && @mappings[term.to_s]
|
299
345
|
end
|
300
346
|
|
301
347
|
##
|
302
|
-
#
|
348
|
+
# Reverse term mapping, typically used for finding aliases for keys.
|
303
349
|
#
|
304
350
|
# Returns either the original value, or a mapping for this value.
|
305
351
|
#
|
@@ -307,51 +353,90 @@ module JSON::LD
|
|
307
353
|
# {"@context": {"id": "@id"}, "@id": "foo"} => {"id": "foo"}
|
308
354
|
#
|
309
355
|
# @param [RDF::URI, String] value
|
310
|
-
# @return [
|
356
|
+
# @return [String]
|
311
357
|
def alias(value)
|
312
|
-
|
358
|
+
iri_to_term.fetch(value, value)
|
313
359
|
end
|
314
|
-
|
360
|
+
|
315
361
|
##
|
316
|
-
# Retrieve term coercion
|
362
|
+
# Retrieve term coercion
|
317
363
|
#
|
318
|
-
# @param [String] property in
|
319
|
-
# @param [RDF::URI, '@id'] value (nil)
|
364
|
+
# @param [String] property in unexpanded form
|
320
365
|
#
|
321
366
|
# @return [RDF::URI, '@id']
|
322
|
-
def coerce(property
|
367
|
+
def coerce(property)
|
323
368
|
# Map property, if it's not an RDF::Value
|
324
|
-
debug("coerce") {"map #{property} to #{mapping(property)}"} if mapping(property)
|
325
|
-
property = mapping(property) if mapping(property)
|
326
369
|
return '@id' if [RDF.type, '@type'].include?(property) # '@type' always is an IRI
|
370
|
+
@coercions.fetch(property, nil)
|
371
|
+
end
|
372
|
+
|
373
|
+
##
|
374
|
+
# Set term coercion
|
375
|
+
#
|
376
|
+
# @param [String] property in unexpanded form
|
377
|
+
# @param [RDF::URI, '@id'] value
|
378
|
+
#
|
379
|
+
# @return [RDF::URI, '@id']
|
380
|
+
def set_coerce(property, value)
|
381
|
+
debug {"coerce #{property.inspect} to #{value.inspect}"} unless @coercions[property.to_s] == value
|
327
382
|
if value
|
328
|
-
|
329
|
-
|
383
|
+
@coercions[property] = value
|
384
|
+
else
|
385
|
+
@coercions.delete(property)
|
330
386
|
end
|
331
|
-
@coercions[property.to_s] if @coercions.has_key?(property.to_s)
|
332
387
|
end
|
333
388
|
|
334
389
|
##
|
335
|
-
# Retrieve
|
390
|
+
# Retrieve container mapping, add it if `value` is provided
|
391
|
+
#
|
392
|
+
# @param [String] property in unexpanded form
|
393
|
+
# @return [String]
|
394
|
+
def container(property)
|
395
|
+
@containers.fetch(property.to_s, nil)
|
396
|
+
end
|
397
|
+
|
398
|
+
##
|
399
|
+
# Set container mapping
|
336
400
|
#
|
337
|
-
# @param [String] property
|
338
|
-
# @param [
|
401
|
+
# @param [String] property
|
402
|
+
# @param [String] value one of @list, @set or nil
|
339
403
|
# @return [Boolean]
|
340
|
-
def
|
341
|
-
|
342
|
-
|
343
|
-
|
404
|
+
def set_container(property, value)
|
405
|
+
return if @containers[property.to_s] == value
|
406
|
+
debug {"coerce #{property.inspect} to #{value.inspect}"}
|
407
|
+
if value
|
408
|
+
@containers[property.to_s] = value
|
409
|
+
else
|
410
|
+
@containers.delete(value)
|
344
411
|
end
|
345
|
-
@lists[property.to_s] && @lists[property.to_s]
|
346
412
|
end
|
347
413
|
|
348
414
|
##
|
349
|
-
#
|
415
|
+
# Retrieve the language associated with a property, or the default language otherwise
|
416
|
+
# @return [String]
|
417
|
+
def language(property)
|
418
|
+
@languages.fetch(property.to_s, @default_language) if !coerce(property)
|
419
|
+
end
|
420
|
+
|
421
|
+
##
|
422
|
+
# Set language mapping
|
423
|
+
#
|
424
|
+
# @param [String] property
|
425
|
+
# @param [String] value
|
426
|
+
# @return [String]
|
427
|
+
def set_language(property, value)
|
428
|
+
# Use false for nil language
|
429
|
+
@languages[property.to_s] = value ? value : false
|
430
|
+
end
|
431
|
+
|
432
|
+
##
|
433
|
+
# Determine if `term` is a suitable term.
|
434
|
+
# Term may be any valid JSON string.
|
350
435
|
#
|
351
436
|
# @param [String] term
|
352
437
|
# @return [Boolean]
|
353
438
|
def term_valid?(term)
|
354
|
-
term.
|
439
|
+
term.is_a?(String)
|
355
440
|
end
|
356
441
|
|
357
442
|
##
|
@@ -368,15 +453,22 @@ module JSON::LD
|
|
368
453
|
# @see http://json-ld.org/spec/latest/json-ld-api/#iri-expansion
|
369
454
|
def expand_iri(iri, options = {})
|
370
455
|
return iri unless iri.is_a?(String)
|
371
|
-
prefix, suffix = iri.split(
|
372
|
-
|
456
|
+
prefix, suffix = iri.split(':', 2)
|
457
|
+
return mapping(iri) if mapping(iri) # If it's an exact match
|
458
|
+
debug("expand_iri") {"prefix: #{prefix.inspect}, suffix: #{suffix.inspect}"} unless options[:quiet]
|
459
|
+
base = self.base unless [:predicate, :datatype].include?(options[:position])
|
373
460
|
prefix = prefix.to_s
|
374
461
|
case
|
375
|
-
when prefix == '_'
|
376
|
-
when iri.to_s[0,1] == "@"
|
377
|
-
when
|
378
|
-
when
|
379
|
-
|
462
|
+
when prefix == '_' && suffix then debug("=> bnode"); bnode(suffix)
|
463
|
+
when iri.to_s[0,1] == "@" then debug("=> keyword"); iri
|
464
|
+
when suffix.to_s[0,2] == '//' then debug("=> iri"); uri(iri)
|
465
|
+
when mappings.has_key?(prefix) then debug("=> curie"); uri(mappings[prefix] + suffix.to_s)
|
466
|
+
when base then debug("=> base"); base.join(iri)
|
467
|
+
else
|
468
|
+
# Otherwise, it must be an absolute IRI
|
469
|
+
u = uri(iri)
|
470
|
+
debug("=> absolute") {"#{u.inspect} abs? #{u.absolute?.inspect}"}
|
471
|
+
u if u.absolute? || [:subject, :object].include?(options[:position])
|
380
472
|
end
|
381
473
|
end
|
382
474
|
|
@@ -387,17 +479,123 @@ module JSON::LD
|
|
387
479
|
# @param [Hash{Symbol => Object}] options ({})
|
388
480
|
# @option options [:subject, :predicate, :object, :datatype] position
|
389
481
|
# Useful when determining how to serialize.
|
482
|
+
# @option options [Object] :value
|
483
|
+
# Value, used to select among various maps for the same IRI
|
484
|
+
# @option options [Boolean] :not_term (false)
|
485
|
+
# Don't return a term, but only a CURIE or IRI.
|
390
486
|
#
|
391
487
|
# @return [String] compacted form of IRI
|
392
488
|
# @see http://json-ld.org/spec/latest/json-ld-api/#iri-compaction
|
393
489
|
def compact_iri(iri, options = {})
|
394
|
-
|
490
|
+
# Don't cause these to be compacted
|
491
|
+
return iri.to_s if [RDF.first, RDF.rest, RDF.nil].include?(iri)
|
492
|
+
return self.alias('@type') if options[:position] == :predicate && iri == RDF.type
|
395
493
|
|
396
494
|
depth(options) do
|
397
|
-
debug {"compact_iri(#{
|
495
|
+
debug {"compact_iri(#{iri.inspect}, #{options.inspect})"}
|
496
|
+
|
497
|
+
value = options.fetch(:value, nil)
|
498
|
+
|
499
|
+
# Get a list of terms which map to iri
|
500
|
+
terms = mappings.keys.select {|t| mapping(t).to_s == iri}
|
501
|
+
|
502
|
+
# Create an association term map for terms to their associated
|
503
|
+
# term rank.
|
504
|
+
term_map = {}
|
505
|
+
|
506
|
+
# If value is a @list add a term rank for each
|
507
|
+
# term mapping to iri which has @container @list.
|
508
|
+
debug("compact_iri", "#{value.inspect} is a list? #{list?(value).inspect}")
|
509
|
+
if list?(value)
|
510
|
+
list_terms = terms.select {|t| container(t) == '@list'}
|
511
|
+
|
512
|
+
term_map = list_terms.inject({}) do |memo, t|
|
513
|
+
memo[t] = term_rank(t, value)
|
514
|
+
memo
|
515
|
+
end unless list_terms.empty?
|
516
|
+
debug("term map") {"remove zero rank terms: #{term_map.keys.select {|t| term_map[t] == 0}}"} if term_map.any? {|t,r| r == 0}
|
517
|
+
term_map.delete_if {|t, r| r == 0}
|
518
|
+
end
|
519
|
+
|
520
|
+
# Otherwise, value is @value or a native type.
|
521
|
+
# Add a term rank for each term mapping to iri
|
522
|
+
# which does not have @container @list
|
523
|
+
if term_map.empty?
|
524
|
+
non_list_terms = terms.reject {|t| container(t) == '@list'}
|
525
|
+
|
526
|
+
# If value is a @list, exclude from term map those terms
|
527
|
+
# with @container @set
|
528
|
+
non_list_terms.reject {|t| container(t) == '@set'} if list?(value)
|
529
|
+
|
530
|
+
term_map = non_list_terms.inject({}) do |memo, t|
|
531
|
+
memo[t] = term_rank(t, value)
|
532
|
+
memo
|
533
|
+
end unless non_list_terms.empty?
|
534
|
+
debug("term map") {"remove zero rank terms: #{term_map.keys.select {|t| term_map[t] == 0}}"} if term_map.any? {|t,r| r == 0}
|
535
|
+
term_map.delete_if {|t, r| r == 0}
|
536
|
+
end
|
537
|
+
|
538
|
+
# If we don't want terms, remove anything that's not a CURIE or IRI
|
539
|
+
term_map.keep_if {|t, v| t.index(':') } if options.fetch(:not_term, false)
|
540
|
+
|
541
|
+
# Find terms having the greatest term match value
|
542
|
+
least_distance = term_map.values.max
|
543
|
+
terms = term_map.keys.select {|t| term_map[t] == least_distance}
|
544
|
+
|
545
|
+
# If the list of found terms is empty, append a compact IRI for
|
546
|
+
# each term which is a prefix of iri which does not have
|
547
|
+
# @type coercion, @container coercion or @language coercion rules
|
548
|
+
# along with the iri itself.
|
549
|
+
if terms.empty?
|
550
|
+
curies = mappings.keys.map {|k| iri.to_s.sub(mapping(k).to_s, "#{k}:") if
|
551
|
+
iri.to_s.index(mapping(k).to_s) == 0 &&
|
552
|
+
iri.to_s != mapping(k).to_s}.compact
|
553
|
+
|
554
|
+
debug("curies") do
|
555
|
+
curies.map do |c|
|
556
|
+
"#{c}: " +
|
557
|
+
"container: #{container(c).inspect}, " +
|
558
|
+
"coerce: #{coerce(c).inspect}, " +
|
559
|
+
"lang: #{language(c).inspect}"
|
560
|
+
end.inspect
|
561
|
+
end
|
398
562
|
|
399
|
-
|
400
|
-
|
563
|
+
terms = curies.select do |curie|
|
564
|
+
container(curie) != '@list' &&
|
565
|
+
coerce(curie).nil? &&
|
566
|
+
language(curie) == default_language
|
567
|
+
end
|
568
|
+
|
569
|
+
debug("curies") {"selected #{terms.inspect}"}
|
570
|
+
|
571
|
+
# If we still don't have any terms and we're using standard_prefixes,
|
572
|
+
# try those, and add to mapping
|
573
|
+
if terms.empty? && @options[:standard_prefixes]
|
574
|
+
terms = RDF::Vocabulary.
|
575
|
+
select {|v| iri.index(v.to_uri.to_s) == 0}.
|
576
|
+
map do |v|
|
577
|
+
prefix = v.__name__.to_s.split('::').last.downcase
|
578
|
+
set_mapping(prefix, v.to_uri.to_s)
|
579
|
+
iri.sub(v.to_uri.to_s, "#{prefix}:").sub(/:$/, '')
|
580
|
+
end
|
581
|
+
debug("curies") {"using standard prefies: #{terms.inspect}"}
|
582
|
+
end
|
583
|
+
|
584
|
+
terms << iri.to_s
|
585
|
+
end
|
586
|
+
|
587
|
+
# Get the first term based on distance and lexecographical order
|
588
|
+
# Prefer terms that don't have @container @set over other terms, unless as set is true
|
589
|
+
terms = terms.sort do |a, b|
|
590
|
+
debug("term sort") {"c(a): #{container(a).inspect}, c(b): #{container(b)}"}
|
591
|
+
if a.length == b.length
|
592
|
+
a <=> b
|
593
|
+
else
|
594
|
+
a.length <=> b.length
|
595
|
+
end
|
596
|
+
end
|
597
|
+
debug("sorted terms") {terms.inspect}
|
598
|
+
result = terms.first
|
401
599
|
|
402
600
|
debug {"=> #{result.inspect}"}
|
403
601
|
result
|
@@ -407,11 +605,11 @@ module JSON::LD
|
|
407
605
|
##
|
408
606
|
# Expand a value from compacted to expanded form making the context
|
409
607
|
# unnecessary. This method is used as part of more general expansion
|
410
|
-
# and operates on RHS values, using a supplied key to determine @type and @
|
608
|
+
# and operates on RHS values, using a supplied key to determine @type and @container
|
411
609
|
# coercion rules.
|
412
610
|
#
|
413
|
-
# @param [
|
414
|
-
# Associated
|
611
|
+
# @param [String] property
|
612
|
+
# Associated property used to find coercion rules
|
415
613
|
# @param [Hash, String] value
|
416
614
|
# Value (literal or IRI) to be expanded
|
417
615
|
# @param [Hash{Symbol => Object}] options
|
@@ -419,39 +617,70 @@ module JSON::LD
|
|
419
617
|
# @return [Hash] Object representation of value
|
420
618
|
# @raise [RDF::ReaderError] if the iri cannot be expanded
|
421
619
|
# @see http://json-ld.org/spec/latest/json-ld-api/#value-expansion
|
422
|
-
def expand_value(
|
620
|
+
def expand_value(property, value, options = {})
|
423
621
|
depth(options) do
|
424
|
-
debug("expand_value") {"
|
622
|
+
debug("expand_value") {"property: #{property.inspect}, value: #{value.inspect}, coerce: #{coerce(property).inspect}"}
|
425
623
|
result = case value
|
426
624
|
when TrueClass, FalseClass, RDF::Literal::Boolean
|
427
|
-
|
625
|
+
case coerce(property)
|
626
|
+
when RDF::XSD.double.to_s
|
627
|
+
{"@value" => value.to_s, "@type" => RDF::XSD.double.to_s}
|
628
|
+
else
|
629
|
+
# Unless there's coercion, to not modify representation
|
630
|
+
value.is_a?(RDF::Literal::Boolean) ? value.object : value
|
631
|
+
end
|
428
632
|
when Integer, RDF::Literal::Integer
|
429
|
-
|
430
|
-
|
431
|
-
|
633
|
+
case coerce(property)
|
634
|
+
when RDF::XSD.double.to_s
|
635
|
+
{"@value" => RDF::Literal::Double.new(value, :canonicalize => true).to_s, "@type" => RDF::XSD.double.to_s}
|
636
|
+
when RDF::XSD.integer.to_s, nil
|
637
|
+
# Unless there's coercion, to not modify representation
|
638
|
+
value.is_a?(RDF::Literal::Integer) ? value.object : value
|
639
|
+
else
|
640
|
+
res = Hash.ordered
|
641
|
+
res['@value'] = value.to_s
|
642
|
+
res['@type'] = coerce(property)
|
643
|
+
res
|
644
|
+
end
|
432
645
|
when Float, RDF::Literal::Double
|
433
|
-
|
646
|
+
case coerce(property)
|
647
|
+
when RDF::XSD.integer.to_s
|
648
|
+
{"@value" => value.to_int.to_s, "@type" => RDF::XSD.integer.to_s}
|
649
|
+
when RDF::XSD.double.to_s
|
650
|
+
{"@value" => RDF::Literal::Double.new(value, :canonicalize => true).to_s, "@type" => RDF::XSD.double.to_s}
|
651
|
+
when nil
|
652
|
+
# Unless there's coercion, to not modify representation
|
653
|
+
value.is_a?(RDF::Literal::Double) ? value.object : value
|
654
|
+
else
|
655
|
+
res = Hash.ordered
|
656
|
+
res['@value'] = value.to_s
|
657
|
+
res['@type'] = coerce(property)
|
658
|
+
res
|
659
|
+
end
|
660
|
+
when BigDecimal, RDF::Literal::Decimal
|
661
|
+
{"@value" => value.to_s, "@type" => RDF::XSD.decimal.to_s}
|
434
662
|
when Date, Time, DateTime
|
435
663
|
l = RDF::Literal(value)
|
436
|
-
{"@
|
437
|
-
when RDF::URI
|
664
|
+
{"@value" => l.to_s, "@type" => l.datatype.to_s}
|
665
|
+
when RDF::URI, RDF::Node
|
438
666
|
{'@id' => value.to_s}
|
439
667
|
when RDF::Literal
|
440
|
-
res = Hash.
|
441
|
-
res['@
|
668
|
+
res = Hash.ordered
|
669
|
+
res['@value'] = value.to_s
|
442
670
|
res['@type'] = value.datatype.to_s if value.has_datatype?
|
443
671
|
res['@language'] = value.language.to_s if value.has_language?
|
444
672
|
res
|
445
673
|
else
|
446
|
-
case coerce(
|
674
|
+
case coerce(property)
|
447
675
|
when '@id'
|
448
676
|
{'@id' => expand_iri(value, :position => :object).to_s}
|
449
677
|
when nil
|
450
|
-
|
678
|
+
debug("expand value") {"lang(prop): #{language(property).inspect}, def: #{default_language.inspect}"}
|
679
|
+
language(property) ? {"@value" => value.to_s, "@language" => language(property)} : value.to_s
|
451
680
|
else
|
452
|
-
res = Hash.
|
453
|
-
res['@
|
454
|
-
res['@type'] = coerce(
|
681
|
+
res = Hash.ordered
|
682
|
+
res['@value'] = value.to_s
|
683
|
+
res['@type'] = coerce(property).to_s
|
455
684
|
res
|
456
685
|
end
|
457
686
|
end
|
@@ -464,8 +693,8 @@ module JSON::LD
|
|
464
693
|
##
|
465
694
|
# Compact a value
|
466
695
|
#
|
467
|
-
# @param [
|
468
|
-
# Associated
|
696
|
+
# @param [String] property
|
697
|
+
# Associated property used to find coercion rules
|
469
698
|
# @param [Hash] value
|
470
699
|
# Value (literal or IRI), in full object representation, to be compacted
|
471
700
|
# @param [Hash{Symbol => Object}] options
|
@@ -473,43 +702,43 @@ module JSON::LD
|
|
473
702
|
# @return [Hash] Object representation of value
|
474
703
|
# @raise [ProcessingError] if the iri cannot be expanded
|
475
704
|
# @see http://json-ld.org/spec/latest/json-ld-api/#value-compaction
|
476
|
-
def compact_value(
|
477
|
-
raise ProcessingError::Lossy, "attempt to compact a non-object value" unless value.is_a?(Hash)
|
705
|
+
def compact_value(property, value, options = {})
|
706
|
+
raise ProcessingError::Lossy, "attempt to compact a non-object value: #{value.inspect}" unless value.is_a?(Hash)
|
478
707
|
|
479
708
|
depth(options) do
|
480
|
-
debug("compact_value") {"
|
709
|
+
debug("compact_value") {"property: #{property.inspect}, value: #{value.inspect}, coerce: #{coerce(property).inspect}"}
|
481
710
|
|
482
711
|
result = case
|
483
712
|
when %w(boolean integer double).any? {|t| expand_iri(value['@type'], :position => :datatype) == RDF::XSD[t]}
|
484
713
|
# Compact native type
|
485
714
|
debug {" (native)"}
|
486
|
-
l = RDF::Literal(value['@
|
715
|
+
l = RDF::Literal(value['@value'], :datatype => expand_iri(value['@type'], :position => :datatype))
|
487
716
|
l.canonicalize.object
|
488
|
-
when coerce(
|
717
|
+
when coerce(property) == '@id' && value.has_key?('@id')
|
489
718
|
# Compact an @id coercion
|
490
719
|
debug {" (@id & coerce)"}
|
491
720
|
compact_iri(value['@id'], :position => :object)
|
492
|
-
when value['@type'] && expand_iri(value['@type'], :position => :datatype) == coerce(
|
721
|
+
when value['@type'] && expand_iri(value['@type'], :position => :datatype) == coerce(property)
|
493
722
|
# Compact common datatype
|
494
|
-
debug {" (@type & coerce) == #{coerce(
|
495
|
-
value['@
|
723
|
+
debug {" (@type & coerce) == #{coerce(property)}"}
|
724
|
+
value['@value']
|
496
725
|
when value.has_key?('@id')
|
497
726
|
# Compact an IRI
|
498
|
-
value['@id'] = compact_iri(value['@id'], :position => :object)
|
499
|
-
debug {" (@id => #{value['@id']})"}
|
727
|
+
value[self.alias('@id')] = compact_iri(value['@id'], :position => :object)
|
728
|
+
debug {" (#{self.alias('@id')} => #{value['@id']})"}
|
500
729
|
value
|
501
|
-
when value['@language'] && value['@language'] == language
|
730
|
+
when value['@language'] && value['@language'] == language(property)
|
502
731
|
# Compact language
|
503
|
-
debug {" (@language) == #{language}"}
|
504
|
-
value['@
|
505
|
-
when value['@
|
732
|
+
debug {" (@language) == #{language(property).inspect}"}
|
733
|
+
value['@value']
|
734
|
+
when value['@value'] && !value['@language'] && !value['@type'] && !coerce(property) && !default_language
|
506
735
|
# Compact simple literal to string
|
507
|
-
debug {" (@
|
508
|
-
value['@
|
736
|
+
debug {" (@value && !@language && !@type && !coerce && !language)"}
|
737
|
+
value['@value']
|
509
738
|
when value['@type']
|
510
739
|
# Compact datatype
|
511
740
|
debug {" (@type)"}
|
512
|
-
value['@type'] = compact_iri(value['@type'], :position => :datatype)
|
741
|
+
value[self.alias('@type')] = compact_iri(value['@type'], :position => :datatype)
|
513
742
|
value
|
514
743
|
else
|
515
744
|
# Otherwise, use original value
|
@@ -534,9 +763,11 @@ module JSON::LD
|
|
534
763
|
|
535
764
|
def inspect
|
536
765
|
v = %w([EvaluationContext)
|
766
|
+
v << "def_language=#{default_language}"
|
767
|
+
v << "languages[#{languages.keys.length}]=#{languages}"
|
537
768
|
v << "mappings[#{mappings.keys.length}]=#{mappings}"
|
538
769
|
v << "coercions[#{coercions.keys.length}]=#{coercions}"
|
539
|
-
v << "
|
770
|
+
v << "containers[#{containers.length}]=#{containers}"
|
540
771
|
v.join(", ") + "]"
|
541
772
|
end
|
542
773
|
|
@@ -545,8 +776,9 @@ module JSON::LD
|
|
545
776
|
ec = super
|
546
777
|
ec.mappings = mappings.dup
|
547
778
|
ec.coercions = coercions.dup
|
548
|
-
ec.
|
549
|
-
ec.
|
779
|
+
ec.containers = containers.dup
|
780
|
+
ec.languages = languages.dup
|
781
|
+
ec.default_language = default_language
|
550
782
|
ec.options = options
|
551
783
|
ec.iri_to_term = iri_to_term.dup
|
552
784
|
ec.iri_to_curie = iri_to_curie.dup
|
@@ -574,67 +806,59 @@ module JSON::LD
|
|
574
806
|
end
|
575
807
|
|
576
808
|
##
|
577
|
-
#
|
578
|
-
#
|
579
|
-
#
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
809
|
+
# Get a "match value" given a term and a value. The value
|
810
|
+
# is lowest when the relative match between the term and the value
|
811
|
+
# is closest.
|
812
|
+
#
|
813
|
+
# @param [String] term
|
814
|
+
# @param [Object] value
|
815
|
+
# @return [Integer]
|
816
|
+
def term_rank(term, value)
|
817
|
+
debug("term rank") { "term: #{term.inspect}, value: #{value.inspect}"}
|
818
|
+
debug("term rank") { "coerce: #{coerce(term).inspect}, lang: #{languages.fetch(term, nil).inspect}"}
|
819
|
+
|
820
|
+
# A term without @language or @type can be used with rank 1 for any value
|
821
|
+
default_term = !coerce(term) && !languages.has_key?(term)
|
822
|
+
debug("term rank") { "default_term: #{default_term.inspect}"}
|
823
|
+
|
824
|
+
rank = case value
|
825
|
+
when TrueClass, FalseClass
|
826
|
+
coerce(term) == RDF::XSD.boolean.to_s ? 3 : (default_term ? 2 : 1)
|
827
|
+
when Integer
|
828
|
+
coerce(term) == RDF::XSD.integer.to_s ? 3 : (default_term ? 2 : 1)
|
829
|
+
when Float
|
830
|
+
coerce(term) == RDF::XSD.double.to_s ? 3 : (default_term ? 2 : 1)
|
831
|
+
when nil
|
832
|
+
# A value of null probably means it's an @id
|
833
|
+
3
|
585
834
|
when String
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
when
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
iri.sub(vocab.to_uri.to_s, "#{prefix}:").sub(/:$/, '')
|
835
|
+
# When compacting a string, the string has no language, so the term can be used if the term has @language null or it is a default term and there is no default language
|
836
|
+
debug("term rank") {"string: lang: #{languages.fetch(term, false).inspect}, def: #{default_language.inspect}"}
|
837
|
+
!languages.fetch(term, true) || (default_term && !default_language) ? 3 : 0
|
838
|
+
when Hash
|
839
|
+
if list?(value)
|
840
|
+
if value['@list'].empty?
|
841
|
+
# If the @list property is an empty array, if term has @container set to @list, term rank is 1, otherwise 0.
|
842
|
+
container(term) == '@list' ? 1 : 0
|
843
|
+
else
|
844
|
+
# Otherwise, return the sum of the term ranks for every entry in the list.
|
845
|
+
depth {value['@list'].inject(0) {|memo, v| memo + term_rank(term, v)}}
|
846
|
+
end
|
847
|
+
elsif subject?(value) || subject_reference?(value)
|
848
|
+
coerce(term) == '@id' ? 3 : (default_term ? 1 : 0)
|
849
|
+
elsif val_type = value.fetch('@type', nil)
|
850
|
+
coerce(term) == val_type ? 3 : (default_term ? 1 : 0)
|
851
|
+
elsif val_lang = value.fetch('@language', nil)
|
852
|
+
val_lang == language(term) ? 3 : (default_term ? 1 : 0)
|
853
|
+
else
|
854
|
+
default_term ? 3 : 0
|
855
|
+
end
|
608
856
|
else
|
609
|
-
|
610
|
-
nil
|
857
|
+
raise ProcessingError, "Unexpected value for term_rank: #{value.inspect}"
|
611
858
|
end
|
612
859
|
|
613
|
-
|
614
|
-
|
615
|
-
raise RDF::WriterError, "Invalid IRI #{resource.inspect}: #{e.message}"
|
616
|
-
end
|
617
|
-
|
618
|
-
# Add debug event to debug array, if specified
|
619
|
-
#
|
620
|
-
# @param [String] message
|
621
|
-
# @yieldreturn [String] appended to message, to allow for lazy-evaulation of message
|
622
|
-
def debug(*args)
|
623
|
-
return unless ::JSON::LD.debug? || @options[:debug]
|
624
|
-
list = args
|
625
|
-
list << yield if block_given?
|
626
|
-
message = " " * (@depth || 0) * 2 + (list.empty? ? "" : list.join(": "))
|
627
|
-
puts message if JSON::LD::debug?
|
628
|
-
@options[:debug] << message if @options[:debug].is_a?(Array)
|
629
|
-
end
|
630
|
-
|
631
|
-
# Increase depth around a method invocation
|
632
|
-
def depth(options = {})
|
633
|
-
old_depth = @depth || 0
|
634
|
-
@depth = (options[:depth] || old_depth) + 1
|
635
|
-
ret = yield
|
636
|
-
@depth = old_depth
|
637
|
-
ret
|
860
|
+
# If term has @container @set, and rank is not 0, increase rank by 1.
|
861
|
+
rank > 0 && container(term) == '@set' ? rank + 1 : rank
|
638
862
|
end
|
639
863
|
end
|
640
864
|
end
|