json-ld 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|