json-ld 0.3.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +2 -2
- data/VERSION +1 -1
- data/lib/json/ld.rb +10 -0
- data/lib/json/ld/api.rb +43 -26
- data/lib/json/ld/compact.rb +119 -17
- data/lib/json/ld/evaluation_context.rb +186 -146
- data/lib/json/ld/expand.rb +192 -86
- data/lib/json/ld/extensions.rb +18 -0
- data/lib/json/ld/flatten.rb +6 -8
- data/lib/json/ld/frame.rb +3 -3
- data/lib/json/ld/to_rdf.rb +4 -6
- data/lib/json/ld/utils.rb +46 -15
- data/spec/compact_spec.rb +130 -113
- data/spec/evaluation_context_spec.rb +230 -43
- data/spec/expand_spec.rb +185 -17
- data/spec/flatten_spec.rb +3 -5
- data/spec/frame_spec.rb +1 -1
- data/spec/suite_compact_spec.rb +4 -1
- data/spec/suite_expand_spec.rb +2 -4
- data/spec/suite_flatten_spec.rb +32 -0
- data/spec/suite_frame_spec.rb +1 -1
- data/spec/suite_helper.rb +0 -3
- data/spec/test-files/test-7-compacted.json +3 -3
- data/spec/test-files/test-7-expanded.json +3 -3
- data/spec/to_rdf_spec.rb +1 -1
- data/spec/writer_spec.rb +1 -1
- metadata +16 -30
data/README.markdown
CHANGED
@@ -220,8 +220,8 @@ Full documentation available on [RubyDoc](http://rubydoc.info/gems/json-ld/file/
|
|
220
220
|
|
221
221
|
## Dependencies
|
222
222
|
* [Ruby](http://ruby-lang.org/) (>= 1.8.7) or (>= 1.8.1 with [Backports][])
|
223
|
-
* [RDF.rb](http://rubygems.org/gems/rdf) (>= 0
|
224
|
-
* [JSON](https://rubygems.org/gems/json) (>= 1.5
|
223
|
+
* [RDF.rb](http://rubygems.org/gems/rdf) (>= 1.0)
|
224
|
+
* [JSON](https://rubygems.org/gems/json) (>= 1.5)
|
225
225
|
|
226
226
|
## Installation
|
227
227
|
The recommended installation method is via [RubyGems](http://rubygems.org/).
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.9.0
|
data/lib/json/ld.rb
CHANGED
@@ -89,6 +89,9 @@ module JSON
|
|
89
89
|
# A list containing another list was detected.
|
90
90
|
LIST_OF_LISTS_DETECTED = 3
|
91
91
|
|
92
|
+
# When processing a language map, a value not a
|
93
|
+
ILLEGAL_LANGUAGE_MAP_DETECTED = 4
|
94
|
+
|
92
95
|
attr_reader :code
|
93
96
|
|
94
97
|
class Lossy < ProcessingError
|
@@ -105,6 +108,13 @@ module JSON
|
|
105
108
|
end
|
106
109
|
end
|
107
110
|
|
111
|
+
class LanguageMap < ProcessingError
|
112
|
+
def initialize(*args)
|
113
|
+
super
|
114
|
+
@code = ILLEGAL_LANGUAGE_MAP_DETECTED
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
108
118
|
class ListOfLists < ProcessingError
|
109
119
|
def initialize(*args)
|
110
120
|
super
|
data/lib/json/ld/api.rb
CHANGED
@@ -28,9 +28,22 @@ module JSON::LD
|
|
28
28
|
OPEN_OPTS = {
|
29
29
|
:headers => %w(Accept: application/ld+json, application/json)
|
30
30
|
}
|
31
|
+
|
32
|
+
# Current input
|
33
|
+
# @!attribute [rw] input
|
34
|
+
# @return [String, #read, Hash, Array]
|
31
35
|
attr_accessor :value
|
36
|
+
|
37
|
+
# Input evaluation context
|
38
|
+
# @!attribute [rw] context
|
39
|
+
# @return [JSON::LD::EvaluationContext]
|
32
40
|
attr_accessor :context
|
33
41
|
|
42
|
+
# Current Blank Node Namer
|
43
|
+
# @!attribute [r] namer
|
44
|
+
# @return [JSON::LD::BlankNodeNamer]
|
45
|
+
attr_reader :namer
|
46
|
+
|
34
47
|
##
|
35
48
|
# Initialize the API, reading in any document and setting global options
|
36
49
|
#
|
@@ -49,13 +62,18 @@ module JSON::LD
|
|
49
62
|
# @option options [Boolean] :optimize (false)
|
50
63
|
# If set to `true`, the JSON-LD processor is allowed to optimize the output of the Compaction Algorithm to produce even compacter representations. The algorithm for compaction optimization is beyond the scope of this specification and thus not defined. Consequently, different implementations *MAY* implement different optimization algorithms.
|
51
64
|
# (Presently, this is a noop).
|
52
|
-
# @option options [Boolean] :
|
53
|
-
#
|
54
|
-
# @option options [Boolean] :useRdfType (false)
|
65
|
+
# @option options [Boolean] :useNativeTypes (true)
|
66
|
+
# If set to `true`, the JSON-LD processor will use native datatypes for expression xsd:integer, xsd:boolean, and xsd:double values, otherwise, it will use the expanded form.
|
67
|
+
# @option options [Boolean] :useRdfType (false)
|
68
|
+
# If set to `true`, the JSON-LD processor will try to convert datatyped literals to JSON native types instead of using the expanded object form when converting from RDF. `xsd:boolean` values will be converted to `true` or `false`. `xsd:integer` and `xsd:double` values will be converted to JSON numbers.
|
69
|
+
# @option options [Boolean] :rename_bnodes (true)
|
70
|
+
# Rename bnodes as part of expansion, or keep them the same.
|
55
71
|
# @yield [api]
|
56
72
|
# @yieldparam [API]
|
57
73
|
def initialize(input, context, options = {}, &block)
|
58
74
|
@options = {:compactArrays => true}.merge(options)
|
75
|
+
options = {:rename_bnodes => true}.merge(options)
|
76
|
+
@namer = options[:rename_bnodes] ? BlankNodeNamer.new("t") : BlankNodeMapper.new
|
59
77
|
@value = case input
|
60
78
|
when Array, Hash then input.dup
|
61
79
|
when IO, StringIO then JSON.parse(input.read)
|
@@ -110,7 +128,7 @@ module JSON::LD
|
|
110
128
|
result = result['@graph'] if result.is_a?(Hash) && result.keys == %w(@graph)
|
111
129
|
|
112
130
|
# Finally, if element is a JSON object, it is wrapped into an array.
|
113
|
-
result = [result] unless result.is_a?(Array)
|
131
|
+
result = [result].compact unless result.is_a?(Array)
|
114
132
|
callback.call(result) if callback
|
115
133
|
yield result if block_given?
|
116
134
|
result
|
@@ -207,18 +225,14 @@ module JSON::LD
|
|
207
225
|
|
208
226
|
# Generate _nodeMap_
|
209
227
|
node_map = Hash.ordered
|
210
|
-
self.generate_node_map(value,
|
211
|
-
node_map,
|
212
|
-
(graph.to_s == '@merged' ? '@merged' : '@default'),
|
213
|
-
nil,
|
214
|
-
BlankNodeNamer.new("t"))
|
228
|
+
self.generate_node_map(value, node_map, (graph.to_s == '@merged' ? '@merged' : '@default'))
|
215
229
|
|
216
230
|
result = []
|
217
231
|
|
218
232
|
# If nodeMap has no property graph, return result, otherwise set definitions to its value.
|
219
233
|
definitions = node_map.fetch(graph.to_s, {})
|
220
234
|
|
221
|
-
# Foreach property and
|
235
|
+
# Foreach property and value of definitions
|
222
236
|
definitions.keys.sort.each do |prop|
|
223
237
|
value = definitions[prop]
|
224
238
|
result << value
|
@@ -304,11 +318,7 @@ module JSON::LD
|
|
304
318
|
# Get framing nodes from expanded input, replacing Blank Node identifiers as necessary
|
305
319
|
all_nodes = Hash.ordered
|
306
320
|
depth do
|
307
|
-
generate_node_map(value,
|
308
|
-
all_nodes,
|
309
|
-
'@merged',
|
310
|
-
nil,
|
311
|
-
BlankNodeNamer.new("t"))
|
321
|
+
generate_node_map(value, all_nodes, '@merged')
|
312
322
|
end
|
313
323
|
@node_map = all_nodes['@merged']
|
314
324
|
debug(".frame") {"node_map: #{@node_map.to_json(JSON_STATE)}"}
|
@@ -350,20 +360,27 @@ module JSON::LD
|
|
350
360
|
# See options in {JSON::LD::API#initialize}
|
351
361
|
# Options passed to {JSON::LD::API.expand}
|
352
362
|
# @raise [InvalidContext]
|
363
|
+
# @return [Array<RDF::Statement>] if no block given
|
353
364
|
# @yield statement
|
354
365
|
# @yieldparam [RDF::Statement] statement
|
355
|
-
def self.toRDF(input, context = nil, callback = nil, options = {})
|
356
|
-
|
357
|
-
|
358
|
-
|
366
|
+
def self.toRDF(input, context = nil, callback = nil, options = {}, &block)
|
367
|
+
API.new(input, context, options) do |api|
|
368
|
+
# 1) Perform the Expansion Algorithm on the JSON-LD input.
|
369
|
+
# This removes any existing context to allow the given context to be cleanly applied.
|
370
|
+
result = api.expand(api.value, nil, api.context)
|
359
371
|
|
360
|
-
|
361
|
-
debug(".expand") {"expanded input: #{value.to_json(JSON_STATE)}"}
|
372
|
+
api.send(:debug, ".expand") {"expanded input: #{result.to_json(JSON_STATE)}"}
|
362
373
|
# Start generating statements
|
363
|
-
|
364
|
-
|
365
|
-
|
374
|
+
results = []
|
375
|
+
api.statements("", result, nil, nil, nil) do |statement|
|
376
|
+
callback ||= block if block_given?
|
377
|
+
if callback
|
378
|
+
callback.call(statement)
|
379
|
+
else
|
380
|
+
results << statement
|
381
|
+
end
|
366
382
|
end
|
383
|
+
results
|
367
384
|
end
|
368
385
|
end
|
369
386
|
|
@@ -384,7 +401,7 @@ module JSON::LD
|
|
384
401
|
# The JSON-LD document in expanded form
|
385
402
|
# @return [Array<Hash>]
|
386
403
|
# The JSON-LD document in expanded form
|
387
|
-
def self.fromRDF(input, callback = nil, options = {})
|
404
|
+
def self.fromRDF(input, callback = nil, options = {}, &block)
|
388
405
|
options = {:useNativeTypes => true}.merge(options)
|
389
406
|
result = nil
|
390
407
|
|
@@ -392,8 +409,8 @@ module JSON::LD
|
|
392
409
|
result = api.from_statements(input)
|
393
410
|
end
|
394
411
|
|
412
|
+
callback ||= block if block_given?
|
395
413
|
callback.call(result) if callback
|
396
|
-
yield result if block_given?
|
397
414
|
result
|
398
415
|
end
|
399
416
|
end
|
data/lib/json/ld/compact.rb
CHANGED
@@ -21,7 +21,7 @@ module JSON::LD
|
|
21
21
|
# active property.
|
22
22
|
debug("compact") {"Array #{element.inspect}"}
|
23
23
|
result = depth {element.map {|v| compact(v, property)}}
|
24
|
-
|
24
|
+
|
25
25
|
# If element has a single member and the active property has no
|
26
26
|
# @container mapping to @list or @set, the compacted value is that
|
27
27
|
# member; otherwise the compacted value is element
|
@@ -35,36 +35,92 @@ module JSON::LD
|
|
35
35
|
when Hash
|
36
36
|
# 2) Otherwise, if element is an object:
|
37
37
|
result = {}
|
38
|
-
|
38
|
+
|
39
39
|
if k = %w(@list @set @value).detect {|container| element.has_key?(container)}
|
40
40
|
debug("compact") {"#{k}: container(#{property}) = #{context.container(property)}"}
|
41
41
|
end
|
42
42
|
|
43
43
|
k ||= '@id' if element.keys == ['@id']
|
44
|
-
|
44
|
+
|
45
45
|
case k
|
46
46
|
when '@value', '@id'
|
47
47
|
# If element has an @value property or element is a node reference, return the result of performing Value Compaction on element using active property.
|
48
48
|
v = context.compact_value(property, element, :depth => @depth)
|
49
|
-
debug("compact") {"value optimization, return as #{v.inspect}"}
|
49
|
+
debug("compact") {"value optimization for #{property}, return as #{v.inspect}"}
|
50
50
|
return v
|
51
51
|
when '@list'
|
52
52
|
# 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 with all null values removed.
|
53
|
-
|
53
|
+
|
54
54
|
# If there already exists a value for active property in element and the full IRI of property is also coerced to @list, return an error.
|
55
55
|
# FIXME: check for full-iri list coercion
|
56
56
|
|
57
57
|
# Otherwise store the resulting array as value of active property if empty or property otherwise.
|
58
|
-
compacted_key = context.compact_iri(
|
58
|
+
compacted_key = context.compact_iri('@list', :position => :predicate, :depth => @depth)
|
59
59
|
v = depth { compact(element[k], property) }
|
60
|
-
|
60
|
+
|
61
61
|
# Return either the result as an array, as an object with a key of @list (or appropriate alias from active context
|
62
62
|
v = [v].compact unless v.is_a?(Array)
|
63
|
-
|
63
|
+
unless context.container(property) == '@list'
|
64
|
+
v = {compacted_key => v}
|
65
|
+
if element['@annotation']
|
66
|
+
compacted_key = context.compact_iri('@annotation', :position => :predicate, :depth => @depth)
|
67
|
+
v[compacted_key] = element['@annotation']
|
68
|
+
end
|
69
|
+
end
|
64
70
|
debug("compact") {"@list result, return as #{v.inspect}"}
|
65
71
|
return v
|
66
72
|
end
|
67
73
|
|
74
|
+
# Check for property generators before continuing with other elements
|
75
|
+
# For each term pg in the active context which is a property generator
|
76
|
+
# Select property generator terms by shortest term
|
77
|
+
context.mappings.keys.sort.each do |term|
|
78
|
+
next unless context.mapping(term).is_a?(Array)
|
79
|
+
# Using the first expanded IRI p associated with the property generator
|
80
|
+
expanded_iris = context.mapping(term).map(&:to_s)
|
81
|
+
p = expanded_iris.first.to_s
|
82
|
+
|
83
|
+
# Skip to the next property generator term unless p is a property of element
|
84
|
+
next unless element.has_key?(p)
|
85
|
+
|
86
|
+
debug("compact") {"check pg #{term}: #{expanded_iris}"}
|
87
|
+
|
88
|
+
# For each node n which is a value of p in element
|
89
|
+
node_values = []
|
90
|
+
element[p].dup.each do |n|
|
91
|
+
# For each expanded IRI pi associated with the property generator other than p
|
92
|
+
next unless expanded_iris[1..-1].all? do |pi|
|
93
|
+
debug("compact") {"check #{pi} for (#{n.inspect})"}
|
94
|
+
element.has_key?(pi) && element[pi].any? do |ni|
|
95
|
+
nodesEquivalent?(n, ni)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Remove n as a value of all p and pi in element
|
100
|
+
debug("compact") {"removed matched value #{n.inspect} from #{expanded_iris.inspect}"}
|
101
|
+
expanded_iris.each do |pi|
|
102
|
+
# FIXME: This removes all values equivalent to n, not just the first
|
103
|
+
element[pi] = element[pi].reject {|ni| nodesEquivalent?(n, ni)}
|
104
|
+
end
|
105
|
+
|
106
|
+
# Add the result of performing the compaction algorithm on n to pg to output
|
107
|
+
node_values << n
|
108
|
+
end
|
109
|
+
|
110
|
+
# If there are node_values, or all the values from expanded_iris are empty, add node_values to result, and remove the expanded_iris as keys from element
|
111
|
+
if node_values.length > 0 || expanded_iris.all? {|pi| element.has_key?(pi) && element[pi].empty?}
|
112
|
+
debug("compact") {"compact extracted pg values"}
|
113
|
+
result[term] = depth { compact(node_values, term)}
|
114
|
+
result[term] = [result[term]] if !result[term].is_a?(Array) && context.container(term) == '@set'
|
115
|
+
|
116
|
+
debug("compact") {"remove empty pg keys from element"}
|
117
|
+
expanded_iris.each do |pi|
|
118
|
+
debug(" =>") {"#{pi}? #{element.fetch(pi, []).empty?}"}
|
119
|
+
element.delete(pi) if element.fetch(pi, []).empty?
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
68
124
|
# Otherwise, for each property and value in element:
|
69
125
|
element.each do |key, value|
|
70
126
|
debug("compact") {"#{key}: #{value.inspect}"}
|
@@ -80,48 +136,63 @@ module JSON::LD
|
|
80
136
|
context.compact_iri(value, :position => position, :depth => @depth)
|
81
137
|
when Array
|
82
138
|
# 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
|
83
|
-
compacted_value = value.map {|
|
139
|
+
compacted_value = value.map {|v2| context.compact_iri(v2, :position => position, :depth => @depth)}
|
84
140
|
debug {" => compacted value(#{key}): #{compacted_value.inspect}"}
|
85
141
|
compacted_value = compacted_value.first if compacted_value.length == 1 && @options[:compactArrays]
|
86
142
|
compacted_value
|
87
143
|
end
|
144
|
+
elsif key == '@annotation' && context.container(property) == '@annotation'
|
145
|
+
# Skip the annotation key if annotations being applied
|
146
|
+
next
|
88
147
|
else
|
89
148
|
if value.empty?
|
90
149
|
# Make sure that an empty array is preserved
|
91
150
|
compacted_key = context.compact_iri(key, :position => :predicate, :depth => @depth)
|
92
151
|
next if compacted_key.nil?
|
93
152
|
result[compacted_key] = value
|
153
|
+
next
|
94
154
|
end
|
95
155
|
|
96
156
|
# For each item in value:
|
97
|
-
|
157
|
+
value = [value] if key == '@annotation' && value.is_a?(String)
|
158
|
+
raise ProcessingError, "found #{value.inspect} for #{key} of #{element.inspect}" unless value.is_a?(Array)
|
98
159
|
value.each do |item|
|
99
160
|
compacted_key = context.compact_iri(key, :position => :predicate, :value => item, :depth => @depth)
|
161
|
+
|
162
|
+
# Result for this item, typically the output object itself
|
163
|
+
item_result = result
|
164
|
+
item_key = compacted_key
|
100
165
|
debug {" => compacted key: #{compacted_key.inspect} for #{item.inspect}"}
|
101
166
|
next if compacted_key.nil?
|
102
167
|
|
168
|
+
# Language maps and annotations
|
169
|
+
if field = %w(@language @annotation).detect {|kk| context.container(compacted_key) == kk}
|
170
|
+
item_result = result[compacted_key] ||= Hash.new
|
171
|
+
item_key = item[field]
|
172
|
+
end
|
173
|
+
|
103
174
|
compacted_item = depth {self.compact(item, compacted_key)}
|
104
175
|
debug {" => compacted value: #{compacted_value.inspect}"}
|
105
|
-
|
106
|
-
case
|
176
|
+
|
177
|
+
case item_result[item_key]
|
107
178
|
when Array
|
108
|
-
|
179
|
+
item_result[item_key] << compacted_item
|
109
180
|
when nil
|
110
181
|
if !compacted_value.is_a?(Array) && context.container(compacted_key) == '@set'
|
111
182
|
compacted_item = [compacted_item].compact
|
112
183
|
debug {" => as @set: #{compacted_item.inspect}"}
|
113
184
|
end
|
114
|
-
|
185
|
+
item_result[item_key] = compacted_item
|
115
186
|
else
|
116
|
-
|
187
|
+
item_result[item_key] = [item_result[item_key], compacted_item]
|
117
188
|
end
|
118
189
|
end
|
119
190
|
end
|
120
191
|
end
|
121
|
-
|
192
|
+
|
122
193
|
# Re-order result keys
|
123
194
|
r = Hash.ordered
|
124
|
-
result.keys.
|
195
|
+
result.keys.kw_sort.each {|kk| r[kk] = result[kk]}
|
125
196
|
r
|
126
197
|
else
|
127
198
|
# For other types, the compacted value is the element value
|
@@ -129,5 +200,36 @@ module JSON::LD
|
|
129
200
|
element
|
130
201
|
end
|
131
202
|
end
|
203
|
+
|
204
|
+
private
|
205
|
+
|
206
|
+
# Determines if two nodes are equivalent.
|
207
|
+
# * Value nodes are equivalent using a deep comparison
|
208
|
+
# * Arrays are equivalent if they have the same number of elements and each element is equivalent to the matching element
|
209
|
+
# * Node Defintions/References are equivalent IFF the have the same @id
|
210
|
+
def nodesEquivalent?(n1, n2)
|
211
|
+
depth do
|
212
|
+
r = if n1.is_a?(Array) && n2.is_a?(Array) && n1.length == n2.length
|
213
|
+
equiv = true
|
214
|
+
n1.each_with_index do |v1, i|
|
215
|
+
equiv &&= nodesEquivalent?(v1, n2[i]) if equiv
|
216
|
+
end
|
217
|
+
equiv
|
218
|
+
elsif value?(n1) && value?(n2)
|
219
|
+
n1 == n2
|
220
|
+
elsif list?(n1)
|
221
|
+
list?(n2) &&
|
222
|
+
n1.fetch('@annotation', true) == n2.fetch('@annotation', true) &&
|
223
|
+
nodesEquivalent?(n1['@list'], n2['@list'])
|
224
|
+
elsif (node?(n1) || node_reference?(n2))
|
225
|
+
(node?(n2) || node_reference?(n2)) && n1['@id'] == n2['@id']
|
226
|
+
else
|
227
|
+
false
|
228
|
+
end
|
229
|
+
|
230
|
+
debug("nodesEquivalent?(#{n1.inspect}, #{n2.inspect}): #{r.inspect}")
|
231
|
+
r
|
232
|
+
end
|
233
|
+
end
|
132
234
|
end
|
133
235
|
end
|
@@ -75,6 +75,10 @@ module JSON::LD
|
|
75
75
|
# @return [EvaluationContext] A context provided to us that we can use without re-serializing
|
76
76
|
attr_accessor :provided_context
|
77
77
|
|
78
|
+
# @!attribute [r] remote_contexts
|
79
|
+
# @return [Array<String>] The list of remote contexts already processed
|
80
|
+
attr_accessor :remote_contexts
|
81
|
+
|
78
82
|
##
|
79
83
|
# Create new evaluation context
|
80
84
|
# @yield [ec]
|
@@ -91,6 +95,7 @@ module JSON::LD
|
|
91
95
|
RDF.to_uri.to_s => "rdf",
|
92
96
|
RDF::XSD.to_uri.to_s => "xsd"
|
93
97
|
}
|
98
|
+
@remote_contexts = []
|
94
99
|
|
95
100
|
@options = options
|
96
101
|
|
@@ -121,6 +126,7 @@ module JSON::LD
|
|
121
126
|
# Load context document, if it is a string
|
122
127
|
begin
|
123
128
|
ctx = JSON.load(context)
|
129
|
+
raise JSON::LD::InvalidContext::LoadError, "Context missing @context key" if @options[:validate] && ctx['@context'].nil?
|
124
130
|
parse(ctx["@context"] || {})
|
125
131
|
rescue JSON::ParserError => e
|
126
132
|
debug("parse") {"Failed to parse @context from remote document at #{context}: #{e.message}"}
|
@@ -137,6 +143,8 @@ module JSON::LD
|
|
137
143
|
ec = nil
|
138
144
|
begin
|
139
145
|
url = expand_iri(context, :base => context_base || base, :position => :subject)
|
146
|
+
raise JSON::LD::InvalidContext::LoadError if remote_contexts.include?(url)
|
147
|
+
@remote_contexts = @remote_contexts + [url]
|
140
148
|
ecdup = self.dup
|
141
149
|
ecdup.context_base = url # Set context_base for recursive remote contexts
|
142
150
|
RDF::Util::File.open_file(url) {|f| ec = ecdup.parse(f)}
|
@@ -162,6 +170,7 @@ module JSON::LD
|
|
162
170
|
new_ec = self.dup
|
163
171
|
new_ec.provided_context = context.dup
|
164
172
|
|
173
|
+
# 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.
|
165
174
|
{
|
166
175
|
'@language' => :default_language=,
|
167
176
|
'@vocab' => :vocab=
|
@@ -171,7 +180,7 @@ module JSON::LD
|
|
171
180
|
context.delete(key)
|
172
181
|
debug("parse") {"Set #{key} to #{v.inspect}"}
|
173
182
|
new_ec.send(setter, v)
|
174
|
-
elsif v
|
183
|
+
elsif v && @options[:validate]
|
175
184
|
raise InvalidContext::Syntax, "#{key.inspect} is invalid"
|
176
185
|
end
|
177
186
|
end
|
@@ -186,26 +195,48 @@ module JSON::LD
|
|
186
195
|
debug("parse") {"Hash[#{key}] = #{value.inspect}"}
|
187
196
|
|
188
197
|
if KEYWORDS.include?(key)
|
189
|
-
raise InvalidContext::Syntax, "key #{key.inspect} must not be a keyword"
|
198
|
+
raise InvalidContext::Syntax, "key #{key.inspect} must not be a keyword" if @options[:validate]
|
199
|
+
next
|
190
200
|
elsif term_valid?(key)
|
191
201
|
# Remove all coercion information for the property
|
192
202
|
new_ec.set_coerce(key, nil)
|
193
203
|
new_ec.set_container(key, nil)
|
194
204
|
@languages.delete(key)
|
195
205
|
|
196
|
-
# Extract IRI mapping. This is complicated, as @id may have been aliased
|
197
|
-
value =
|
198
|
-
|
206
|
+
# Extract IRI mapping. This is complicated, as @id may have been aliased. Also, if @id is explicitly set to nil, it inhibits and automatic mapping, so treat it as false, to distinguish from no mapping at all.
|
207
|
+
value = case value
|
208
|
+
when Hash
|
209
|
+
value.has_key?('@id') && value['@id'].nil? ? false : value.fetch('@id', nil)
|
210
|
+
when nil
|
211
|
+
false
|
212
|
+
else
|
213
|
+
value
|
214
|
+
end
|
215
|
+
|
216
|
+
# Explicitly say this is not mapped
|
217
|
+
if value == false
|
218
|
+
debug("parse") {"Map #{key} to nil"}
|
219
|
+
new_ec.set_mapping(key, nil)
|
220
|
+
next
|
221
|
+
end
|
222
|
+
|
223
|
+
iri = if value.is_a?(Array)
|
224
|
+
# expand each item according the IRI Expansion algorithm. If an item does not expand to a valid absolute IRI, raise an INVALID_PROPERTY_GENERATOR error; otherwise sort val and store it as IRI mapping in definition.
|
225
|
+
value.map do |v|
|
226
|
+
raise InvalidContext::Syntax, "unknown mapping for #{key.inspect} to #{v.inspect}" unless v.is_a?(String)
|
227
|
+
new_ec.expand_iri(v, :position => :predicate)
|
228
|
+
end.sort
|
229
|
+
elsif value
|
230
|
+
raise InvalidContext::Syntax, "unknown mapping for #{key.inspect} to #{value.inspect}" unless value.is_a?(String)
|
231
|
+
new_ec.expand_iri(value, :position => :predicate)
|
232
|
+
end
|
199
233
|
|
200
|
-
iri = new_ec.expand_iri(value, :position => :predicate) if value.is_a?(String)
|
201
234
|
if iri && new_ec.mappings.fetch(key, nil) != iri
|
202
235
|
# Record term definition
|
203
236
|
new_ec.set_mapping(key, iri)
|
204
237
|
num_updates += 1
|
205
|
-
elsif value.nil?
|
206
|
-
new_ec.set_mapping(key, nil)
|
207
238
|
end
|
208
|
-
|
239
|
+
elsif @options[:validate]
|
209
240
|
raise InvalidContext::Syntax, "key #{key.inspect} is invalid"
|
210
241
|
end
|
211
242
|
end
|
@@ -223,14 +254,18 @@ module JSON::LD
|
|
223
254
|
iri = new_ec.expand_iri(value2, :position => :predicate) if value2.is_a?(String)
|
224
255
|
case key2
|
225
256
|
when '@type'
|
226
|
-
raise InvalidContext::Syntax, "unknown mapping for '@type' to #{value2.
|
257
|
+
raise InvalidContext::Syntax, "unknown mapping for '@type' to #{value2.inspect}" unless value2.is_a?(String) || value2.nil?
|
227
258
|
if new_ec.coerce(key) != iri
|
228
|
-
|
259
|
+
case iri
|
260
|
+
when '@id', /_:/, RDF::Node
|
261
|
+
else
|
262
|
+
raise InvalidContext::Syntax, "unknown mapping for '@type' to #{iri.inspect}" unless (RDF::URI(iri).absolute? rescue false)
|
263
|
+
end
|
229
264
|
# Record term coercion
|
230
265
|
new_ec.set_coerce(key, iri)
|
231
266
|
end
|
232
267
|
when '@container'
|
233
|
-
raise InvalidContext::Syntax, "unknown mapping for '@container' to #{value2.
|
268
|
+
raise InvalidContext::Syntax, "unknown mapping for '@container' to #{value2.inspect}" unless %w(@list @set @language @annotation).include?(value2)
|
234
269
|
if new_ec.container(key) != value2
|
235
270
|
debug("parse") {"container #{key.inspect} as #{value2.inspect}"}
|
236
271
|
new_ec.set_container(key, value2)
|
@@ -281,10 +316,10 @@ module JSON::LD
|
|
281
316
|
ctx['@vocab'] = vocab.to_s if vocab
|
282
317
|
|
283
318
|
# Mappings
|
284
|
-
mappings.keys.
|
319
|
+
mappings.keys.kw_sort{|a, b| a.to_s <=> b.to_s}.each do |k|
|
285
320
|
next unless term_valid?(k.to_s)
|
286
321
|
debug {"=> mappings[#{k}] => #{mappings[k]}"}
|
287
|
-
ctx[k] = mappings[k]
|
322
|
+
ctx[k] = mappings[k]
|
288
323
|
end
|
289
324
|
|
290
325
|
unless coercions.empty? && containers.empty? && languages.empty?
|
@@ -310,7 +345,7 @@ module JSON::LD
|
|
310
345
|
end
|
311
346
|
|
312
347
|
debug {"=> container(#{k}) => #{container(k)}"}
|
313
|
-
if %w(@list @set).include?(container(k))
|
348
|
+
if %w(@list @set @language @annotation).include?(container(k))
|
314
349
|
ctx[k]["@container"] = container(k)
|
315
350
|
debug {"=> container[#{k}] => #{container(k).inspect}"}
|
316
351
|
end
|
@@ -344,14 +379,14 @@ module JSON::LD
|
|
344
379
|
#
|
345
380
|
# @return [RDF::URI, String]
|
346
381
|
def mapping(term)
|
347
|
-
@mappings.fetch(term.to_s,
|
382
|
+
@mappings.fetch(term.to_s, false)
|
348
383
|
end
|
349
384
|
|
350
385
|
##
|
351
386
|
# Set term mapping
|
352
387
|
#
|
353
388
|
# @param [#to_s] term
|
354
|
-
# @param [RDF::URI, String] value
|
389
|
+
# @param [RDF::URI, String, nil] value
|
355
390
|
#
|
356
391
|
# @return [RDF::URI, String]
|
357
392
|
def set_mapping(term, value)
|
@@ -388,8 +423,8 @@ module JSON::LD
|
|
388
423
|
# @return [RDF::URI, '@id']
|
389
424
|
def coerce(property)
|
390
425
|
# Map property, if it's not an RDF::Value
|
391
|
-
# @type
|
392
|
-
return '@id' if [RDF.type, '@type'
|
426
|
+
# @type is always is an IRI
|
427
|
+
return '@id' if [RDF.type, '@type'].include?(property)
|
393
428
|
@coercions.fetch(property, nil)
|
394
429
|
end
|
395
430
|
|
@@ -415,6 +450,7 @@ module JSON::LD
|
|
415
450
|
# @param [String] property in unexpanded form
|
416
451
|
# @return [String]
|
417
452
|
def container(property)
|
453
|
+
return '@set' if property == '@graph'
|
418
454
|
@containers.fetch(property.to_s, nil)
|
419
455
|
end
|
420
456
|
|
@@ -472,33 +508,60 @@ module JSON::LD
|
|
472
508
|
# Useful when determining how to serialize.
|
473
509
|
# @option options [RDF::URI] base (self.base)
|
474
510
|
# Base IRI to use when expanding relative IRIs.
|
475
|
-
#
|
476
|
-
#
|
511
|
+
# @option options [Array<String>] path ([])
|
512
|
+
# Array of looked up iris, used to find cycles
|
513
|
+
# @option options [BlankNodeNamer] namer
|
514
|
+
# Blank Node namer to use for renaming Blank Nodes
|
515
|
+
#
|
516
|
+
# @return [RDF::Term, String, Array<RDF::URI>]
|
517
|
+
# IRI or String, if it's a keyword, or array of IRI, if it matches
|
518
|
+
# a property generator
|
477
519
|
# @raise [RDF::ReaderError] if the iri cannot be expanded
|
478
520
|
# @see http://json-ld.org/spec/latest/json-ld-api/#iri-expansion
|
479
521
|
def expand_iri(iri, options = {})
|
480
522
|
return iri unless iri.is_a?(String)
|
523
|
+
|
481
524
|
prefix, suffix = iri.split(':', 2)
|
482
|
-
|
525
|
+
unless (m = mapping(iri)) == false
|
526
|
+
# It's an exact match
|
527
|
+
debug("expand_iri") {"match: #{iri.inspect} to #{m.inspect}"} unless options[:quiet]
|
528
|
+
return case m
|
529
|
+
when nil
|
530
|
+
nil
|
531
|
+
when Array
|
532
|
+
# Return array of IRIs, if it's a property generator
|
533
|
+
m.map {|mm| uri(mm.to_s, options[:namer])}
|
534
|
+
else
|
535
|
+
uri(m.to_s, options[:namer])
|
536
|
+
end
|
537
|
+
end
|
483
538
|
debug("expand_iri") {"prefix: #{prefix.inspect}, suffix: #{suffix.inspect}, vocab: #{vocab.inspect}"} unless options[:quiet]
|
484
|
-
base = [:subject].include?(options[:position]) ? options.fetch(:base, self.base) : nil
|
539
|
+
base = [:subject, :type].include?(options[:position]) ? options.fetch(:base, self.base) : nil
|
485
540
|
prefix = prefix.to_s
|
486
541
|
case
|
487
|
-
when prefix == '_' && suffix then bnode(suffix)
|
542
|
+
when prefix == '_' && suffix then uri(bnode(suffix), options[:namer])
|
488
543
|
when iri.to_s[0,1] == "@" then iri
|
489
544
|
when suffix.to_s[0,2] == '//' then uri(iri)
|
490
|
-
when
|
545
|
+
when (mapping = mapping(prefix)) != false
|
546
|
+
debug("expand_iri") {"mapping: #{mapping(prefix).inspect}"} unless options[:quiet]
|
547
|
+
case mapping
|
548
|
+
when Array
|
549
|
+
# Return array of IRIs, if it's a property generator
|
550
|
+
mapping.map {|m| uri(m.to_s + suffix.to_s, options[:namer])}
|
551
|
+
else
|
552
|
+
uri(mapping.to_s + suffix.to_s, options[:namer])
|
553
|
+
end
|
491
554
|
when base then base.join(iri)
|
492
555
|
when vocab then uri("#{vocab}#{iri}")
|
493
556
|
else
|
494
557
|
# Otherwise, it must be an absolute IRI
|
495
558
|
u = uri(iri)
|
496
|
-
u if u.absolute? || [:subject].include?(options[:position])
|
559
|
+
u if u.absolute? || [:subject, :type].include?(options[:position])
|
497
560
|
end
|
498
561
|
end
|
499
562
|
|
500
563
|
##
|
501
|
-
#
|
564
|
+
# Compacts an absolute IRI to the shortest matching term or compact IRI
|
502
565
|
#
|
503
566
|
# @param [RDF::URI] iri
|
504
567
|
# @param [Hash{Symbol => Object}] options ({})
|
@@ -524,10 +587,9 @@ module JSON::LD
|
|
524
587
|
# Create an empty list of terms _terms_ that will be populated with terms that are ranked according to how closely they match value. Initialize highest rank to 0, and set a flag list container to false.
|
525
588
|
terms = {}
|
526
589
|
|
527
|
-
# If value is a @list
|
528
|
-
#
|
529
|
-
|
530
|
-
if list?(value)
|
590
|
+
# If value is a @list select terms that match every item equivalently.
|
591
|
+
debug("compact_iri", "#{value.inspect} is a list? #{list?(value).inspect}") if value
|
592
|
+
if list?(value) && !annotation?(value)
|
531
593
|
list_terms = matched_terms.select {|t| container(t) == '@list'}
|
532
594
|
|
533
595
|
terms = list_terms.inject({}) do |memo, t|
|
@@ -563,13 +625,6 @@ module JSON::LD
|
|
563
625
|
least_distance = terms.values.max
|
564
626
|
terms = terms.keys.select {|t| terms[t] == least_distance}
|
565
627
|
|
566
|
-
# If terms is empty, and the active context has a @vocab which is a prefix of iri where the resulting relative IRI is not a term in the active context. The resulting relative IRI is the unmatched part of iri.
|
567
|
-
if vocab && terms.empty? && iri.to_s.index(vocab) == 0 &&
|
568
|
-
[:predicate, :type].include?(options[:position])
|
569
|
-
terms << iri.to_s.sub(vocab, '')
|
570
|
-
debug("vocab") {"vocab: #{vocab}, rel: #{terms.first}"}
|
571
|
-
end
|
572
|
-
|
573
628
|
# If terms is empty, add a compact IRI representation of iri for each
|
574
629
|
# term in the active context which maps to an IRI which is a prefix for
|
575
630
|
# iri where the resulting compact IRI is not a term in the active
|
@@ -605,6 +660,15 @@ module JSON::LD
|
|
605
660
|
debug("curies") {"selected #{terms.inspect}"}
|
606
661
|
end
|
607
662
|
|
663
|
+
# If terms is empty, and the active context has a @vocab which is a prefix of iri where the resulting relative IRI is not a term in the active context. The resulting relative IRI is the unmatched part of iri.
|
664
|
+
# Don't use vocab, if the result would collide with a term
|
665
|
+
if vocab && terms.empty? && iri.to_s.index(vocab) == 0 &&
|
666
|
+
!mapping(iri.to_s.sub(vocab, '')) &&
|
667
|
+
[:predicate, :type].include?(options[:position])
|
668
|
+
terms << iri.to_s.sub(vocab, '')
|
669
|
+
debug("vocab") {"vocab: #{vocab}, rel: #{terms.first}"}
|
670
|
+
end
|
671
|
+
|
608
672
|
# If we still don't have any terms and we're using standard_prefixes,
|
609
673
|
# try those, and add to mapping
|
610
674
|
if terms.empty? && @options[:standard_prefixes]
|
@@ -650,8 +714,8 @@ module JSON::LD
|
|
650
714
|
##
|
651
715
|
# Expand a value from compacted to expanded form making the context
|
652
716
|
# unnecessary. This method is used as part of more general expansion
|
653
|
-
# and operates on RHS values, using a supplied key to determine @type and
|
654
|
-
# coercion rules.
|
717
|
+
# and operates on RHS values, using a supplied key to determine @type and
|
718
|
+
# @container coercion rules.
|
655
719
|
#
|
656
720
|
# @param [String] property
|
657
721
|
# Associated property used to find coercion rules
|
@@ -659,6 +723,8 @@ module JSON::LD
|
|
659
723
|
# Value (literal or IRI) to be expanded
|
660
724
|
# @param [Hash{Symbol => Object}] options
|
661
725
|
# @option options [Boolean] :useNativeTypes (true) use native representations
|
726
|
+
# @option options [BlankNodeNamer] namer
|
727
|
+
# Blank Node namer to use for renaming Blank Nodes
|
662
728
|
#
|
663
729
|
# @return [Hash] Object representation of value
|
664
730
|
# @raise [RDF::ReaderError] if the iri cannot be expanded
|
@@ -667,95 +733,40 @@ module JSON::LD
|
|
667
733
|
options = {:useNativeTypes => true}.merge(options)
|
668
734
|
depth(options) do
|
669
735
|
debug("expand_value") {"property: #{property.inspect}, value: #{value.inspect}, coerce: #{coerce(property).inspect}"}
|
670
|
-
value = RDF::Literal(value) if RDF::Literal(value).has_datatype?
|
671
|
-
dt = case value
|
672
|
-
when RDF::Literal
|
673
|
-
case value.datatype
|
674
|
-
when RDF::XSD.boolean, RDF::XSD.integer, RDF::XSD.double then value.datatype
|
675
|
-
else value
|
676
|
-
end
|
677
|
-
when RDF::Term then value.class.name
|
678
|
-
else value
|
679
|
-
end
|
680
736
|
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
{"@value" => value.to_s, "@type" => RDF::XSD.boolean.to_s}
|
693
|
-
end
|
694
|
-
end
|
695
|
-
when RDF::XSD.integer
|
696
|
-
debug("xsd:integer")
|
697
|
-
case coerce(property)
|
698
|
-
when RDF::XSD.double.to_s
|
699
|
-
{"@value" => RDF::Literal::Double.new(value, :canonicalize => true).to_s, "@type" => RDF::XSD.double.to_s}
|
700
|
-
when RDF::XSD.integer.to_s, nil
|
701
|
-
# Unless there's coercion, to not modify representation
|
702
|
-
if options[:useNativeTypes]
|
703
|
-
{"@value" => value.is_a?(RDF::Literal::Integer) ? value.object : value}
|
704
|
-
else
|
705
|
-
{"@value" => value.to_s, "@type" => RDF::XSD.integer.to_s}
|
706
|
-
end
|
707
|
-
else
|
708
|
-
res = Hash.ordered
|
709
|
-
res['@value'] = value.to_s
|
710
|
-
res['@type'] = coerce(property)
|
711
|
-
res
|
712
|
-
end
|
713
|
-
when RDF::XSD.double
|
714
|
-
debug("xsd:double")
|
715
|
-
case coerce(property)
|
716
|
-
when RDF::XSD.integer.to_s
|
717
|
-
{"@value" => value.to_int.to_s, "@type" => RDF::XSD.integer.to_s}
|
718
|
-
when RDF::XSD.double.to_s
|
719
|
-
{"@value" => RDF::Literal::Double.new(value, :canonicalize => true).to_s, "@type" => RDF::XSD.double.to_s}
|
720
|
-
when nil
|
721
|
-
if options[:useNativeTypes]
|
722
|
-
# Unless there's coercion, to not modify representation
|
723
|
-
{"@value" => value.is_a?(RDF::Literal::Double) ? value.object : value}
|
724
|
-
else
|
725
|
-
{"@value" => RDF::Literal::Double.new(value, :canonicalize => true).to_s, "@type" => RDF::XSD.double.to_s}
|
726
|
-
end
|
727
|
-
else
|
728
|
-
res = Hash.ordered
|
729
|
-
res['@value'] = value.to_s
|
730
|
-
res['@type'] = coerce(property)
|
731
|
-
res
|
732
|
-
end
|
733
|
-
when "RDF::URI", "RDF::Node"
|
737
|
+
value = if value.is_a?(RDF::Value)
|
738
|
+
value
|
739
|
+
elsif coerce(property) == '@id'
|
740
|
+
expand_iri(value, :position => :subject, :namer => options[:namer])
|
741
|
+
else
|
742
|
+
RDF::Literal(value)
|
743
|
+
end
|
744
|
+
debug("expand_value") {"normalized: #{value.inspect}"}
|
745
|
+
|
746
|
+
result = case value
|
747
|
+
when RDF::URI, RDF::Node
|
734
748
|
debug("URI | BNode") { value.to_s }
|
735
749
|
{'@id' => value.to_s}
|
736
750
|
when RDF::Literal
|
737
|
-
debug("Literal")
|
751
|
+
debug("Literal") {"datatype: #{value.datatype.inspect}"}
|
738
752
|
res = Hash.ordered
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
res
|
743
|
-
else
|
744
|
-
debug("else")
|
745
|
-
case coerce(property)
|
746
|
-
when '@id'
|
747
|
-
{'@id' => expand_iri(value, :position => :subject).to_s}
|
748
|
-
when nil
|
749
|
-
debug("expand value") {"lang(prop): #{language(property).inspect}, def: #{default_language.inspect}"}
|
750
|
-
language(property) ? {"@value" => value.to_s, "@language" => language(property)} : {"@value" => value.to_s}
|
753
|
+
if options[:useNativeTypes] && [RDF::XSD.boolean, RDF::XSD.integer, RDF::XSD.double].include?(value.datatype)
|
754
|
+
res['@value'] = value.object
|
755
|
+
res['@type'] = uri(coerce(property), options[:namer]) if coerce(property)
|
751
756
|
else
|
752
|
-
|
757
|
+
value.canonicalize! if value.datatype == RDF::XSD.double
|
753
758
|
res['@value'] = value.to_s
|
754
|
-
|
755
|
-
|
759
|
+
if coerce(property)
|
760
|
+
res['@type'] = uri(coerce(property), options[:namer]).to_s
|
761
|
+
elsif value.has_datatype?
|
762
|
+
res['@type'] = uri(value.datatype, options[:namer]).to_s
|
763
|
+
elsif value.has_language? || language(property)
|
764
|
+
res['@language'] = (value.language || language(property)).to_s
|
765
|
+
end
|
756
766
|
end
|
767
|
+
res
|
757
768
|
end
|
758
|
-
|
769
|
+
|
759
770
|
debug {"=> #{result.inspect}"}
|
760
771
|
result
|
761
772
|
end
|
@@ -780,12 +791,14 @@ module JSON::LD
|
|
780
791
|
depth(options) do
|
781
792
|
debug("compact_value") {"property: #{property.inspect}, value: #{value.inspect}, coerce: #{coerce(property).inspect}"}
|
782
793
|
|
794
|
+
# Remove @annotation if property has annotation
|
795
|
+
value.delete('@annotation') if container(property) == '@annotation'
|
796
|
+
|
783
797
|
result = case
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
# l.canonicalize.object
|
798
|
+
when value.has_key?('@annotation')
|
799
|
+
# Don't compact the value
|
800
|
+
debug {" (@annotation without container @annotation)"}
|
801
|
+
value
|
789
802
|
when coerce(property) == '@id' && value.has_key?('@id')
|
790
803
|
# Compact an @id coercion
|
791
804
|
debug {" (@id & coerce)"}
|
@@ -799,11 +812,11 @@ module JSON::LD
|
|
799
812
|
value[self.alias('@id')] = compact_iri(value['@id'], :position => :subject)
|
800
813
|
debug {" (#{self.alias('@id')} => #{value['@id']})"}
|
801
814
|
value
|
802
|
-
when value['@language'] && value['@language'] == language(property)
|
815
|
+
when value['@language'] && (value['@language'] == language(property) || container(property) == '@language')
|
803
816
|
# Compact language
|
804
817
|
debug {" (@language) == #{language(property).inspect}"}
|
805
818
|
value['@value']
|
806
|
-
when value
|
819
|
+
when !value.fetch('@value', "").is_a?(String)
|
807
820
|
# Compact simple literal to string
|
808
821
|
debug {" (@value not string)"}
|
809
822
|
value['@value']
|
@@ -853,27 +866,36 @@ module JSON::LD
|
|
853
866
|
|
854
867
|
def dup
|
855
868
|
# Also duplicate mappings, coerce and list
|
869
|
+
that = self
|
856
870
|
ec = super
|
857
|
-
ec.
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
871
|
+
ec.instance_eval do
|
872
|
+
@mappings = that.mappings.dup
|
873
|
+
@coerceions = that.coercions.dup
|
874
|
+
@containers = that.containers.dup
|
875
|
+
@languages = that.languages.dup
|
876
|
+
@default_language = that.default_language
|
877
|
+
@options = that.options
|
878
|
+
@iri_to_term = that.iri_to_term.dup
|
879
|
+
@iri_to_curie = that.iri_to_curie.dup
|
880
|
+
end
|
865
881
|
ec
|
866
882
|
end
|
867
883
|
|
868
884
|
private
|
869
885
|
|
870
|
-
def uri(value,
|
871
|
-
value
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
886
|
+
def uri(value, namer = nil)
|
887
|
+
case value.to_s
|
888
|
+
when /^_:(.*)$/
|
889
|
+
# Map BlankNodes if a namer is given
|
890
|
+
debug "uri(bnode)#{value}: #{$1}"
|
891
|
+
bnode(namer ? namer.get_sym($1) : $1)
|
892
|
+
else
|
893
|
+
value = RDF::URI.new(value)
|
894
|
+
value.validate! if @options[:validate]
|
895
|
+
value.canonicalize! if @options[:canonicalize]
|
896
|
+
value = RDF::URI.intern(value) if @options[:intern]
|
897
|
+
value
|
898
|
+
end
|
877
899
|
end
|
878
900
|
|
879
901
|
# Keep track of allocated BNodes
|
@@ -910,25 +932,42 @@ module JSON::LD
|
|
910
932
|
elsif list?(value)
|
911
933
|
if value['@list'].empty?
|
912
934
|
# If the @list property is an empty array, if term has @container set to @list, term rank is 1, otherwise 0.
|
935
|
+
debug("term rank") { "empty list"}
|
913
936
|
container(term) == '@list' ? 1 : 0
|
914
937
|
else
|
915
|
-
|
916
|
-
|
938
|
+
debug("term rank") { "non-empty list"}
|
939
|
+
# Otherwise, return the most specific term, for which the term has some match against every value.
|
940
|
+
depth {value['@list'].map {|v| term_rank(term, v)}}.min
|
917
941
|
end
|
918
942
|
elsif value?(value)
|
919
943
|
val_type = value.fetch('@type', nil)
|
920
944
|
val_lang = value['@language'] || false if value.has_key?('@language')
|
921
945
|
debug("term rank") {"@val_type: #{val_type.inspect}, val_lang: #{val_lang.inspect}"}
|
922
946
|
if val_type
|
947
|
+
debug("term rank") { "typed value"}
|
923
948
|
coerce(term) == val_type ? 3 : (default_term ? 1 : 0)
|
924
949
|
elsif !value['@value'].is_a?(String)
|
950
|
+
debug("term rank") { "native value"}
|
925
951
|
default_term ? 2 : 1
|
926
952
|
elsif val_lang.nil?
|
927
953
|
debug("val_lang.nil") {"#{language(term).inspect} && #{coerce(term).inspect}"}
|
928
|
-
language(term) == false || (default_term && default_language.nil?)
|
954
|
+
if language(term) == false || (default_term && default_language.nil?)
|
955
|
+
# Value has no language, and there is no default language and the term has no language
|
956
|
+
3
|
957
|
+
elsif default_term
|
958
|
+
# The term has no language (or type), but it's different than the default
|
959
|
+
2
|
960
|
+
else
|
961
|
+
0
|
962
|
+
end
|
929
963
|
else
|
930
|
-
|
964
|
+
debug("val_lang") {"#{language(term).inspect} && #{coerce(term).inspect}"}
|
965
|
+
if val_lang && container(term) == '@language'
|
931
966
|
3
|
967
|
+
elsif val_lang == language(term) || (default_term && default_language == val_lang)
|
968
|
+
2
|
969
|
+
elsif default_term && container(term) == '@set'
|
970
|
+
2 # Choose a set term before a non-set term, if there's a language
|
932
971
|
elsif default_term
|
933
972
|
1
|
934
973
|
else
|
@@ -936,6 +975,7 @@ module JSON::LD
|
|
936
975
|
end
|
937
976
|
end
|
938
977
|
else # node definition/reference
|
978
|
+
debug("node dev/ref")
|
939
979
|
coerce(term) == '@id' ? 3 : (default_term ? 1 : 0)
|
940
980
|
end
|
941
981
|
|