json-ld 0.9.1 → 1.0.0
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/{README.markdown → README.md} +15 -3
- data/VERSION +1 -1
- data/lib/json/ld.rb +50 -87
- data/lib/json/ld/api.rb +85 -96
- data/lib/json/ld/compact.rb +103 -170
- data/lib/json/ld/context.rb +1137 -0
- data/lib/json/ld/expand.rb +212 -171
- data/lib/json/ld/extensions.rb +17 -1
- data/lib/json/ld/flatten.rb +145 -78
- data/lib/json/ld/frame.rb +1 -1
- data/lib/json/ld/from_rdf.rb +73 -103
- data/lib/json/ld/reader.rb +3 -1
- data/lib/json/ld/resource.rb +3 -3
- data/lib/json/ld/to_rdf.rb +98 -109
- data/lib/json/ld/utils.rb +54 -4
- data/lib/json/ld/writer.rb +5 -5
- data/spec/api_spec.rb +3 -28
- data/spec/compact_spec.rb +76 -113
- data/spec/{evaluation_context_spec.rb → context_spec.rb} +307 -563
- data/spec/expand_spec.rb +163 -187
- data/spec/flatten_spec.rb +119 -114
- data/spec/frame_spec.rb +5 -5
- data/spec/from_rdf_spec.rb +44 -24
- data/spec/suite_compact_spec.rb +11 -8
- data/spec/suite_error_expand_spec.rb +23 -0
- data/spec/suite_expand_spec.rb +3 -7
- data/spec/suite_flatten_spec.rb +3 -3
- data/spec/suite_frame_spec.rb +6 -6
- data/spec/suite_from_rdf_spec.rb +3 -3
- data/spec/suite_helper.rb +13 -6
- data/spec/suite_to_rdf_spec.rb +16 -10
- data/spec/test-files/test-1-rdf.ttl +4 -3
- data/spec/test-files/test-3-rdf.ttl +2 -1
- data/spec/test-files/test-4-compacted.json +1 -1
- data/spec/test-files/test-5-rdf.ttl +3 -2
- data/spec/test-files/test-6-rdf.ttl +3 -2
- data/spec/test-files/test-7-compacted.json +3 -3
- data/spec/test-files/test-7-expanded.json +3 -3
- data/spec/test-files/test-7-rdf.ttl +7 -6
- data/spec/test-files/test-9-compacted.json +1 -1
- data/spec/to_rdf_spec.rb +67 -75
- data/spec/writer_spec.rb +2 -0
- metadata +36 -24
- checksums.yaml +0 -15
- data/lib/json/ld/evaluation_context.rb +0 -984
data/spec/writer_spec.rb
CHANGED
metadata
CHANGED
@@ -1,18 +1,20 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: json-ld
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Gregg Kellogg
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2013-
|
12
|
+
date: 2013-05-10 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: rdf
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
16
18
|
requirements:
|
17
19
|
- - ! '>='
|
18
20
|
- !ruby/object:Gem::Version
|
@@ -20,6 +22,7 @@ dependencies:
|
|
20
22
|
type: :runtime
|
21
23
|
prerelease: false
|
22
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
23
26
|
requirements:
|
24
27
|
- - ! '>='
|
25
28
|
- !ruby/object:Gem::Version
|
@@ -27,6 +30,7 @@ dependencies:
|
|
27
30
|
- !ruby/object:Gem::Dependency
|
28
31
|
name: json
|
29
32
|
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
30
34
|
requirements:
|
31
35
|
- - ! '>='
|
32
36
|
- !ruby/object:Gem::Version
|
@@ -34,6 +38,7 @@ dependencies:
|
|
34
38
|
type: :runtime
|
35
39
|
prerelease: false
|
36
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
37
42
|
requirements:
|
38
43
|
- - ! '>='
|
39
44
|
- !ruby/object:Gem::Version
|
@@ -41,6 +46,7 @@ dependencies:
|
|
41
46
|
- !ruby/object:Gem::Dependency
|
42
47
|
name: equivalent-xml
|
43
48
|
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
44
50
|
requirements:
|
45
51
|
- - ! '>='
|
46
52
|
- !ruby/object:Gem::Version
|
@@ -48,6 +54,7 @@ dependencies:
|
|
48
54
|
type: :development
|
49
55
|
prerelease: false
|
50
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
51
58
|
requirements:
|
52
59
|
- - ! '>='
|
53
60
|
- !ruby/object:Gem::Version
|
@@ -55,6 +62,7 @@ dependencies:
|
|
55
62
|
- !ruby/object:Gem::Dependency
|
56
63
|
name: open-uri-cached
|
57
64
|
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
58
66
|
requirements:
|
59
67
|
- - ! '>='
|
60
68
|
- !ruby/object:Gem::Version
|
@@ -62,6 +70,7 @@ dependencies:
|
|
62
70
|
type: :development
|
63
71
|
prerelease: false
|
64
72
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
65
74
|
requirements:
|
66
75
|
- - ! '>='
|
67
76
|
- !ruby/object:Gem::Version
|
@@ -69,6 +78,7 @@ dependencies:
|
|
69
78
|
- !ruby/object:Gem::Dependency
|
70
79
|
name: yard
|
71
80
|
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
72
82
|
requirements:
|
73
83
|
- - ! '>='
|
74
84
|
- !ruby/object:Gem::Version
|
@@ -76,6 +86,7 @@ dependencies:
|
|
76
86
|
type: :development
|
77
87
|
prerelease: false
|
78
88
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
79
90
|
requirements:
|
80
91
|
- - ! '>='
|
81
92
|
- !ruby/object:Gem::Version
|
@@ -83,6 +94,7 @@ dependencies:
|
|
83
94
|
- !ruby/object:Gem::Dependency
|
84
95
|
name: rspec
|
85
96
|
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
86
98
|
requirements:
|
87
99
|
- - ! '>='
|
88
100
|
- !ruby/object:Gem::Version
|
@@ -90,6 +102,7 @@ dependencies:
|
|
90
102
|
type: :development
|
91
103
|
prerelease: false
|
92
104
|
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
93
106
|
requirements:
|
94
107
|
- - ! '>='
|
95
108
|
- !ruby/object:Gem::Version
|
@@ -97,6 +110,7 @@ dependencies:
|
|
97
110
|
- !ruby/object:Gem::Dependency
|
98
111
|
name: rdf-spec
|
99
112
|
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
100
114
|
requirements:
|
101
115
|
- - ! '>='
|
102
116
|
- !ruby/object:Gem::Version
|
@@ -104,6 +118,7 @@ dependencies:
|
|
104
118
|
type: :development
|
105
119
|
prerelease: false
|
106
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
107
122
|
requirements:
|
108
123
|
- - ! '>='
|
109
124
|
- !ruby/object:Gem::Version
|
@@ -111,6 +126,7 @@ dependencies:
|
|
111
126
|
- !ruby/object:Gem::Dependency
|
112
127
|
name: rdf-turtle
|
113
128
|
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
114
130
|
requirements:
|
115
131
|
- - ! '>='
|
116
132
|
- !ruby/object:Gem::Version
|
@@ -118,6 +134,7 @@ dependencies:
|
|
118
134
|
type: :development
|
119
135
|
prerelease: false
|
120
136
|
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
121
138
|
requirements:
|
122
139
|
- - ! '>='
|
123
140
|
- !ruby/object:Gem::Version
|
@@ -125,6 +142,7 @@ dependencies:
|
|
125
142
|
- !ruby/object:Gem::Dependency
|
126
143
|
name: rdf-trig
|
127
144
|
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
128
146
|
requirements:
|
129
147
|
- - ! '>='
|
130
148
|
- !ruby/object:Gem::Version
|
@@ -132,6 +150,7 @@ dependencies:
|
|
132
150
|
type: :development
|
133
151
|
prerelease: false
|
134
152
|
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
135
154
|
requirements:
|
136
155
|
- - ! '>='
|
137
156
|
- !ruby/object:Gem::Version
|
@@ -139,6 +158,7 @@ dependencies:
|
|
139
158
|
- !ruby/object:Gem::Dependency
|
140
159
|
name: rdf-isomorphic
|
141
160
|
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
142
162
|
requirements:
|
143
163
|
- - ! '>='
|
144
164
|
- !ruby/object:Gem::Version
|
@@ -146,6 +166,7 @@ dependencies:
|
|
146
166
|
type: :development
|
147
167
|
prerelease: false
|
148
168
|
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
149
170
|
requirements:
|
150
171
|
- - ! '>='
|
151
172
|
- !ruby/object:Gem::Version
|
@@ -153,6 +174,7 @@ dependencies:
|
|
153
174
|
- !ruby/object:Gem::Dependency
|
154
175
|
name: rdf-xsd
|
155
176
|
requirement: !ruby/object:Gem::Requirement
|
177
|
+
none: false
|
156
178
|
requirements:
|
157
179
|
- - ! '>='
|
158
180
|
- !ruby/object:Gem::Version
|
@@ -160,20 +182,7 @@ dependencies:
|
|
160
182
|
type: :development
|
161
183
|
prerelease: false
|
162
184
|
version_requirements: !ruby/object:Gem::Requirement
|
163
|
-
|
164
|
-
- - ! '>='
|
165
|
-
- !ruby/object:Gem::Version
|
166
|
-
version: '0'
|
167
|
-
- !ruby/object:Gem::Dependency
|
168
|
-
name: backports
|
169
|
-
requirement: !ruby/object:Gem::Requirement
|
170
|
-
requirements:
|
171
|
-
- - ! '>='
|
172
|
-
- !ruby/object:Gem::Version
|
173
|
-
version: '0'
|
174
|
-
type: :runtime
|
175
|
-
prerelease: false
|
176
|
-
version_requirements: !ruby/object:Gem::Requirement
|
185
|
+
none: false
|
177
186
|
requirements:
|
178
187
|
- - ! '>='
|
179
188
|
- !ruby/object:Gem::Version
|
@@ -187,12 +196,12 @@ extensions: []
|
|
187
196
|
extra_rdoc_files: []
|
188
197
|
files:
|
189
198
|
- AUTHORS
|
190
|
-
- README.
|
199
|
+
- README.md
|
191
200
|
- UNLICENSE
|
192
201
|
- VERSION
|
193
202
|
- lib/json/ld/api.rb
|
194
203
|
- lib/json/ld/compact.rb
|
195
|
-
- lib/json/ld/
|
204
|
+
- lib/json/ld/context.rb
|
196
205
|
- lib/json/ld/expand.rb
|
197
206
|
- lib/json/ld/extensions.rb
|
198
207
|
- lib/json/ld/flatten.rb
|
@@ -208,7 +217,7 @@ files:
|
|
208
217
|
- lib/json/ld.rb
|
209
218
|
- spec/api_spec.rb
|
210
219
|
- spec/compact_spec.rb
|
211
|
-
- spec/
|
220
|
+
- spec/context_spec.rb
|
212
221
|
- spec/expand_spec.rb
|
213
222
|
- spec/flatten_spec.rb
|
214
223
|
- spec/format_spec.rb
|
@@ -219,6 +228,7 @@ files:
|
|
219
228
|
- spec/resource_spec.rb
|
220
229
|
- spec/spec_helper.rb
|
221
230
|
- spec/suite_compact_spec.rb
|
231
|
+
- spec/suite_error_expand_spec.rb
|
222
232
|
- spec/suite_expand_spec.rb
|
223
233
|
- spec/suite_flatten_spec.rb
|
224
234
|
- spec/suite_frame_spec.rb
|
@@ -288,31 +298,32 @@ files:
|
|
288
298
|
homepage: http://github.com/gkellogg/json-ld
|
289
299
|
licenses:
|
290
300
|
- Public Domain
|
291
|
-
metadata: {}
|
292
301
|
post_install_message:
|
293
302
|
rdoc_options: []
|
294
303
|
require_paths:
|
295
304
|
- lib
|
296
305
|
required_ruby_version: !ruby/object:Gem::Requirement
|
306
|
+
none: false
|
297
307
|
requirements:
|
298
308
|
- - ! '>='
|
299
309
|
- !ruby/object:Gem::Version
|
300
|
-
version: 1.
|
310
|
+
version: 1.9.3
|
301
311
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
312
|
+
none: false
|
302
313
|
requirements:
|
303
314
|
- - ! '>='
|
304
315
|
- !ruby/object:Gem::Version
|
305
316
|
version: '0'
|
306
317
|
requirements: []
|
307
318
|
rubyforge_project: json-ld
|
308
|
-
rubygems_version:
|
319
|
+
rubygems_version: 1.8.25
|
309
320
|
signing_key:
|
310
|
-
specification_version:
|
321
|
+
specification_version: 3
|
311
322
|
summary: JSON-LD reader/writer for Ruby.
|
312
323
|
test_files:
|
313
324
|
- spec/api_spec.rb
|
314
325
|
- spec/compact_spec.rb
|
315
|
-
- spec/
|
326
|
+
- spec/context_spec.rb
|
316
327
|
- spec/expand_spec.rb
|
317
328
|
- spec/flatten_spec.rb
|
318
329
|
- spec/format_spec.rb
|
@@ -323,6 +334,7 @@ test_files:
|
|
323
334
|
- spec/resource_spec.rb
|
324
335
|
- spec/spec_helper.rb
|
325
336
|
- spec/suite_compact_spec.rb
|
337
|
+
- spec/suite_error_expand_spec.rb
|
326
338
|
- spec/suite_expand_spec.rb
|
327
339
|
- spec/suite_flatten_spec.rb
|
328
340
|
- spec/suite_frame_spec.rb
|
checksums.yaml
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
---
|
2
|
-
!binary "U0hBMQ==":
|
3
|
-
metadata.gz: !binary |-
|
4
|
-
ZGE3NDYzNTY3ZDZhZGNhZjFjMzVjY2Q2YTFhOTQ1MmNmMDk4MTUxYg==
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
NTZhNTg4NDUzYWMwMjVjOGE1NDc4MmY4ZDI0Y2VjNGY0NWZkZmViYg==
|
7
|
-
!binary "U0hBNTEy":
|
8
|
-
metadata.gz: !binary |-
|
9
|
-
M2RjNDUzNDg3YTA5MTcyYmM5ZDdhM2UwYjllZTc0YWM3MzUzODM2MzUwYzI1
|
10
|
-
ZDBjOTFiOGI0ZGUxMjM3MjA3MmFmZDAxZjgxZDkwYWRiYWRiODJiNDdlMjcz
|
11
|
-
ZTMwYjlkZjhiZTk1YmE2NjI2MjI1ZmU5YmNiMDIyMzk0ZmZhZmI=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
NWI0YzUzMDk2ODRmMjM3YmY1Mzc2OTkyNzM5MDNhMTgzNjEzOTFiNTQyMDVm
|
14
|
-
NTYzZmIxMWViZjk2NTMxNjMxNWNiZGYxOThhY2RlZDYwMTExZjM2YWIzYmRm
|
15
|
-
M2Q5OWU3OTU1MzRiNjM1NzIyMTY4MWQxNDdlYWQxMTg2ODY1MWE=
|
@@ -1,984 +0,0 @@
|
|
1
|
-
require 'open-uri'
|
2
|
-
require 'json'
|
3
|
-
require 'bigdecimal'
|
4
|
-
|
5
|
-
module JSON::LD
|
6
|
-
class EvaluationContext
|
7
|
-
include Utils
|
8
|
-
|
9
|
-
# The base.
|
10
|
-
#
|
11
|
-
# @!attribute [rw] base
|
12
|
-
# @return [RDF::URI] Document base IRI, used for expanding relative IRIs.
|
13
|
-
attr_reader :base
|
14
|
-
|
15
|
-
# @!attribute [rw] context_base
|
16
|
-
# @return [RDF::URI] base IRI of the context, if loaded remotely.
|
17
|
-
attr_accessor :context_base
|
18
|
-
|
19
|
-
# @!attribute [rw] mappings
|
20
|
-
# @return [Hash{String => String}] A list of current, in-scope mappings from term to IRI.
|
21
|
-
attr_accessor :mappings
|
22
|
-
|
23
|
-
# @!attribute [rw] iri_to_curie
|
24
|
-
# @return [Hash{RDF::URI => String}] Reverse mappings from IRI to a term or CURIE
|
25
|
-
attr_accessor :iri_to_curie
|
26
|
-
|
27
|
-
# @!attribute [rw] iri_to_term
|
28
|
-
# @return [Hash{RDF::URI => String}] Reverse mappings from IRI to term only for terms, not CURIEs
|
29
|
-
attr_accessor :iri_to_term
|
30
|
-
|
31
|
-
# Type coersion
|
32
|
-
#
|
33
|
-
# The @type keyword is used to specify type coersion rules for the data. For each key in the map, the key is a String representation of the property for which String values will be coerced and the value is the datatype (or @id) to coerce to. Type coersion for the value `@id` asserts that all vocabulary terms listed should undergo coercion to an IRI, including CURIE processing for compact IRI Expressions like `foaf:homepage`.
|
34
|
-
#
|
35
|
-
# @!attribute [rw] coercions
|
36
|
-
# @return [Hash{String => String}]
|
37
|
-
attr_accessor :coercions
|
38
|
-
|
39
|
-
# List coercion
|
40
|
-
#
|
41
|
-
# The @container keyword is used to specify how arrays are to be treated. A value of @list indicates that arrays of values are to be treated as an ordered list. A value of @set indicates that arrays are to be treated as unordered and that singular values are always coerced to an array form on expansion and compaction.
|
42
|
-
# @!attribute [rw] containers
|
43
|
-
# @return [Hash{String => String}]
|
44
|
-
attr_accessor :containers
|
45
|
-
|
46
|
-
# Language coercion
|
47
|
-
#
|
48
|
-
# The @language keyword is used to specify language coercion rules for the data. For each key in the map, the key is a String representation of the property for which String values will be coerced and the value is the language to coerce to. If no property-specific language is given, any default language from the context is used.
|
49
|
-
#
|
50
|
-
# @!attribute [rw] languages
|
51
|
-
# @return [Hash{String => String}]
|
52
|
-
attr_accessor :languages
|
53
|
-
|
54
|
-
# Default language
|
55
|
-
#
|
56
|
-
#
|
57
|
-
# This adds a language to plain strings that aren't otherwise coerced
|
58
|
-
# @!attribute [rw] default_language
|
59
|
-
# @return [String]
|
60
|
-
attr_accessor :default_language
|
61
|
-
|
62
|
-
# Default vocabulary
|
63
|
-
#
|
64
|
-
# Sets the default vocabulary used for expanding terms which
|
65
|
-
# aren't otherwise absolute IRIs
|
66
|
-
# @!attribute [rw] vocab
|
67
|
-
# @return [String]
|
68
|
-
attr_accessor :vocab
|
69
|
-
|
70
|
-
# @!attribute [rw] options
|
71
|
-
# @return [Hash{Symbol => Object}] Global options used in generating IRIs
|
72
|
-
attr_accessor :options
|
73
|
-
|
74
|
-
# @!attribute [rw] provided_context
|
75
|
-
# @return [EvaluationContext] A context provided to us that we can use without re-serializing
|
76
|
-
attr_accessor :provided_context
|
77
|
-
|
78
|
-
# @!attribute [r] remote_contexts
|
79
|
-
# @return [Array<String>] The list of remote contexts already processed
|
80
|
-
attr_accessor :remote_contexts
|
81
|
-
|
82
|
-
##
|
83
|
-
# Create new evaluation context
|
84
|
-
# @yield [ec]
|
85
|
-
# @yieldparam [EvaluationContext]
|
86
|
-
# @return [EvaluationContext]
|
87
|
-
def initialize(options = {})
|
88
|
-
@base = RDF::URI(options[:base]) if options[:base]
|
89
|
-
@mappings = {}
|
90
|
-
@coercions = {}
|
91
|
-
@containers = {}
|
92
|
-
@languages = {}
|
93
|
-
@iri_to_curie = {}
|
94
|
-
@iri_to_term = {
|
95
|
-
RDF.to_uri.to_s => "rdf",
|
96
|
-
RDF::XSD.to_uri.to_s => "xsd"
|
97
|
-
}
|
98
|
-
@remote_contexts = []
|
99
|
-
|
100
|
-
@options = options
|
101
|
-
|
102
|
-
# Load any defined prefixes
|
103
|
-
(options[:prefixes] || {}).each_pair do |k, v|
|
104
|
-
@iri_to_term[v.to_s] = k unless k.nil?
|
105
|
-
end
|
106
|
-
|
107
|
-
debug("init") {"iri_to_term: #{iri_to_term.inspect}"}
|
108
|
-
|
109
|
-
yield(self) if block_given?
|
110
|
-
end
|
111
|
-
|
112
|
-
# Create an Evaluation Context using an existing context as a start by parsing the input.
|
113
|
-
#
|
114
|
-
# @param [String, #read, Array, Hash, EvaluatoinContext] context
|
115
|
-
# @raise [InvalidContext]
|
116
|
-
# on a remote context load error, syntax error, or a reference to a term which is not defined.
|
117
|
-
def parse(context)
|
118
|
-
case context
|
119
|
-
when EvaluationContext
|
120
|
-
debug("parse") {"context: #{context.inspect}"}
|
121
|
-
context.dup
|
122
|
-
when IO, StringIO
|
123
|
-
debug("parse") {"io: #{context}"}
|
124
|
-
# Load context document, if it is a string
|
125
|
-
begin
|
126
|
-
ctx = JSON.load(context)
|
127
|
-
raise JSON::LD::InvalidContext::LoadError, "Context missing @context key" if @options[:validate] && ctx['@context'].nil?
|
128
|
-
parse(ctx["@context"] || {})
|
129
|
-
rescue JSON::ParserError => e
|
130
|
-
debug("parse") {"Failed to parse @context from remote document at #{context}: #{e.message}"}
|
131
|
-
raise JSON::LD::InvalidContext::Syntax, "Failed to parse remote context at #{context}: #{e.message}" if @options[:validate]
|
132
|
-
self.dup
|
133
|
-
end
|
134
|
-
when nil
|
135
|
-
debug("parse") {"nil"}
|
136
|
-
# Load context document, if it is a string
|
137
|
-
ec = EvaluationContext.new(options)
|
138
|
-
when String
|
139
|
-
debug("parse") {"remote: #{context}, base: #{context_base || base}"}
|
140
|
-
# Load context document, if it is a string
|
141
|
-
ec = nil
|
142
|
-
begin
|
143
|
-
url = expand_iri(context, :base => context_base || base, :position => :subject)
|
144
|
-
raise JSON::LD::InvalidContext::LoadError if remote_contexts.include?(url)
|
145
|
-
@remote_contexts = @remote_contexts + [url]
|
146
|
-
ecdup = self.dup
|
147
|
-
ecdup.context_base = url # Set context_base for recursive remote contexts
|
148
|
-
RDF::Util::File.open_file(url) {|f| ec = ecdup.parse(f)}
|
149
|
-
ec.provided_context = context
|
150
|
-
ec.context_base = url
|
151
|
-
debug("parse") {"=> provided_context: #{context.inspect}"}
|
152
|
-
ec
|
153
|
-
rescue Exception => e
|
154
|
-
debug("parse") {"Failed to retrieve @context from remote document at #{context.inspect}: #{e.message}"}
|
155
|
-
raise JSON::LD::InvalidContext::LoadError, "Failed to retrieve remote context at #{context.inspect}: #{e.message}", e.backtrace if @options[:validate]
|
156
|
-
self.dup
|
157
|
-
end
|
158
|
-
when Array
|
159
|
-
# Process each member of the array in order, updating the active context
|
160
|
-
# Updates evaluation context serially during parsing
|
161
|
-
debug("parse") {"Array"}
|
162
|
-
ec = self
|
163
|
-
context.each {|c| ec = ec.parse(c)}
|
164
|
-
ec.provided_context = context
|
165
|
-
debug("parse") {"=> provided_context: #{context.inspect}"}
|
166
|
-
ec
|
167
|
-
when Hash
|
168
|
-
new_ec = self.dup
|
169
|
-
new_ec.provided_context = context.dup
|
170
|
-
|
171
|
-
# 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.
|
172
|
-
{
|
173
|
-
'@language' => :default_language=,
|
174
|
-
'@vocab' => :vocab=
|
175
|
-
}.each do |key, setter|
|
176
|
-
v = context.fetch(key, false)
|
177
|
-
if v.nil? || v.is_a?(String)
|
178
|
-
context.delete(key)
|
179
|
-
debug("parse") {"Set #{key} to #{v.inspect}"}
|
180
|
-
new_ec.send(setter, v)
|
181
|
-
elsif v && @options[:validate]
|
182
|
-
raise InvalidContext::Syntax, "#{key.inspect} is invalid"
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
num_updates = 1
|
187
|
-
while num_updates > 0 do
|
188
|
-
num_updates = 0
|
189
|
-
|
190
|
-
# Map terms to IRIs/keywords first
|
191
|
-
context.each do |key, value|
|
192
|
-
# Expand a string value, unless it matches a keyword
|
193
|
-
debug("parse") {"Hash[#{key}] = #{value.inspect}"}
|
194
|
-
|
195
|
-
if KEYWORDS.include?(key)
|
196
|
-
raise InvalidContext::Syntax, "key #{key.inspect} must not be a keyword" if @options[:validate]
|
197
|
-
next
|
198
|
-
elsif term_valid?(key)
|
199
|
-
# Remove all coercion information for the property
|
200
|
-
new_ec.set_coerce(key, nil)
|
201
|
-
new_ec.set_container(key, nil)
|
202
|
-
@languages.delete(key)
|
203
|
-
|
204
|
-
# 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.
|
205
|
-
value = case value
|
206
|
-
when Hash
|
207
|
-
value.has_key?('@id') && value['@id'].nil? ? false : value.fetch('@id', nil)
|
208
|
-
when nil
|
209
|
-
false
|
210
|
-
else
|
211
|
-
value
|
212
|
-
end
|
213
|
-
|
214
|
-
# Explicitly say this is not mapped
|
215
|
-
if value == false
|
216
|
-
debug("parse") {"Map #{key} to nil"}
|
217
|
-
new_ec.set_mapping(key, nil)
|
218
|
-
next
|
219
|
-
end
|
220
|
-
|
221
|
-
iri = if value.is_a?(Array)
|
222
|
-
# 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.
|
223
|
-
value.map do |v|
|
224
|
-
raise InvalidContext::Syntax, "unknown mapping for #{key.inspect} to #{v.inspect}" unless v.is_a?(String)
|
225
|
-
new_ec.expand_iri(v, :position => :predicate)
|
226
|
-
end.sort
|
227
|
-
elsif value
|
228
|
-
raise InvalidContext::Syntax, "unknown mapping for #{key.inspect} to #{value.inspect}" unless value.is_a?(String)
|
229
|
-
new_ec.expand_iri(value, :position => :predicate)
|
230
|
-
end
|
231
|
-
|
232
|
-
if iri && new_ec.mappings.fetch(key, nil) != iri
|
233
|
-
# Record term definition
|
234
|
-
new_ec.set_mapping(key, iri)
|
235
|
-
num_updates += 1
|
236
|
-
end
|
237
|
-
elsif @options[:validate]
|
238
|
-
raise InvalidContext::Syntax, "key #{key.inspect} is invalid"
|
239
|
-
end
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
|
-
# Next, look for coercion using new_ec
|
244
|
-
context.each do |key, value|
|
245
|
-
# Expand a string value, unless it matches a keyword
|
246
|
-
debug("parse") {"coercion/list: Hash[#{key}] = #{value.inspect}"}
|
247
|
-
case value
|
248
|
-
when Hash
|
249
|
-
# Must have one of @id, @language, @type or @container
|
250
|
-
raise InvalidContext::Syntax, "mapping for #{key.inspect} missing one of @id, @language, @type or @container" if (%w(@id @language @type @container) & value.keys).empty?
|
251
|
-
value.each do |key2, value2|
|
252
|
-
iri = new_ec.expand_iri(value2, :position => :predicate) if value2.is_a?(String)
|
253
|
-
case key2
|
254
|
-
when '@type'
|
255
|
-
raise InvalidContext::Syntax, "unknown mapping for '@type' to #{value2.inspect}" unless value2.is_a?(String) || value2.nil?
|
256
|
-
if new_ec.coerce(key) != iri
|
257
|
-
case iri
|
258
|
-
when '@id', /_:/, RDF::Node
|
259
|
-
else
|
260
|
-
raise InvalidContext::Syntax, "unknown mapping for '@type' to #{iri.inspect}" unless (RDF::URI(iri).absolute? rescue false)
|
261
|
-
end
|
262
|
-
# Record term coercion
|
263
|
-
new_ec.set_coerce(key, iri)
|
264
|
-
end
|
265
|
-
when '@container'
|
266
|
-
raise InvalidContext::Syntax, "unknown mapping for '@container' to #{value2.inspect}" unless %w(@list @set @language @index).include?(value2)
|
267
|
-
if new_ec.container(key) != value2
|
268
|
-
debug("parse") {"container #{key.inspect} as #{value2.inspect}"}
|
269
|
-
new_ec.set_container(key, value2)
|
270
|
-
end
|
271
|
-
when '@language'
|
272
|
-
if !new_ec.languages.has_key?(key) || new_ec.languages[key] != value2
|
273
|
-
debug("parse") {"language #{key.inspect} as #{value2.inspect}"}
|
274
|
-
new_ec.set_language(key, value2)
|
275
|
-
end
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
# If value has no @id, create a mapping from key
|
280
|
-
# to the expanded key IRI
|
281
|
-
unless value.has_key?('@id')
|
282
|
-
iri = new_ec.expand_iri(key, :position => :predicate)
|
283
|
-
new_ec.set_mapping(key, iri)
|
284
|
-
end
|
285
|
-
when nil, String
|
286
|
-
# handled in previous loop
|
287
|
-
else
|
288
|
-
raise InvalidContext::Syntax, "attempt to map #{key.inspect} to #{value.class}"
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
new_ec
|
293
|
-
end
|
294
|
-
end
|
295
|
-
|
296
|
-
##
|
297
|
-
# Generate @context
|
298
|
-
#
|
299
|
-
# If a context was supplied in global options, use that, otherwise, generate one
|
300
|
-
# from this representation.
|
301
|
-
#
|
302
|
-
# @param [Hash{Symbol => Object}] options ({})
|
303
|
-
# @return [Hash]
|
304
|
-
def serialize(options = {})
|
305
|
-
depth(options) do
|
306
|
-
use_context = if provided_context
|
307
|
-
debug "serlialize: reuse context: #{provided_context.inspect}"
|
308
|
-
provided_context
|
309
|
-
else
|
310
|
-
debug("serlialize: generate context")
|
311
|
-
debug {"=> context: #{inspect}"}
|
312
|
-
ctx = Hash.ordered
|
313
|
-
ctx['@language'] = default_language.to_s if default_language
|
314
|
-
ctx['@vocab'] = vocab.to_s if vocab
|
315
|
-
|
316
|
-
# Mappings
|
317
|
-
mappings.keys.kw_sort{|a, b| a.to_s <=> b.to_s}.each do |k|
|
318
|
-
next unless term_valid?(k.to_s)
|
319
|
-
debug {"=> mappings[#{k}] => #{mappings[k]}"}
|
320
|
-
ctx[k] = mappings[k]
|
321
|
-
end
|
322
|
-
|
323
|
-
unless coercions.empty? && containers.empty? && languages.empty?
|
324
|
-
# Coerce
|
325
|
-
(coercions.keys + containers.keys + languages.keys).uniq.sort.each do |k|
|
326
|
-
next if k == '@type'
|
327
|
-
|
328
|
-
# Turn into long form
|
329
|
-
ctx[k] ||= Hash.ordered
|
330
|
-
if ctx[k].is_a?(String)
|
331
|
-
defn = Hash.ordered
|
332
|
-
defn["@id"] = compact_iri(ctx[k], :position => :subject, :not_term => true)
|
333
|
-
ctx[k] = defn
|
334
|
-
end
|
335
|
-
|
336
|
-
debug {"=> coerce(#{k}) => #{coerce(k)}"}
|
337
|
-
if coerce(k) && !NATIVE_DATATYPES.include?(coerce(k))
|
338
|
-
dt = coerce(k)
|
339
|
-
dt = compact_iri(dt, :position => :type) unless dt == '@id'
|
340
|
-
# Fold into existing definition
|
341
|
-
ctx[k]["@type"] = dt
|
342
|
-
debug {"=> datatype[#{k}] => #{dt}"}
|
343
|
-
end
|
344
|
-
|
345
|
-
debug {"=> container(#{k}) => #{container(k)}"}
|
346
|
-
if %w(@list @set @language @index).include?(container(k))
|
347
|
-
ctx[k]["@container"] = container(k)
|
348
|
-
debug {"=> container[#{k}] => #{container(k).inspect}"}
|
349
|
-
end
|
350
|
-
|
351
|
-
debug {"=> language(#{k}) => #{language(k)}"}
|
352
|
-
if language(k) != default_language
|
353
|
-
ctx[k]["@language"] = language(k) ? language(k) : nil
|
354
|
-
debug {"=> language[#{k}] => #{language(k).inspect}"}
|
355
|
-
end
|
356
|
-
|
357
|
-
# Remove an empty definition
|
358
|
-
ctx.delete(k) if ctx[k].empty?
|
359
|
-
end
|
360
|
-
end
|
361
|
-
|
362
|
-
debug {"start_doc: context=#{ctx.inspect}"}
|
363
|
-
ctx
|
364
|
-
end
|
365
|
-
|
366
|
-
# Return hash with @context, or empty
|
367
|
-
r = Hash.ordered
|
368
|
-
r['@context'] = use_context unless use_context.nil? || use_context.empty?
|
369
|
-
r
|
370
|
-
end
|
371
|
-
end
|
372
|
-
|
373
|
-
##
|
374
|
-
# Retrieve term mapping
|
375
|
-
#
|
376
|
-
# @param [String, #to_s] term
|
377
|
-
#
|
378
|
-
# @return [RDF::URI, String]
|
379
|
-
def mapping(term)
|
380
|
-
@mappings.fetch(term.to_s, false)
|
381
|
-
end
|
382
|
-
|
383
|
-
##
|
384
|
-
# Set term mapping
|
385
|
-
#
|
386
|
-
# @param [#to_s] term
|
387
|
-
# @param [RDF::URI, String, nil] value
|
388
|
-
#
|
389
|
-
# @return [RDF::URI, String]
|
390
|
-
def set_mapping(term, value)
|
391
|
-
term = term.to_s
|
392
|
-
term_sym = term.empty? ? "" : term.to_sym
|
393
|
-
# raise InvalidContext::Syntax, "mapping term #{term.inspect} must be a string" unless term.is_a?(String)
|
394
|
-
# 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)
|
395
|
-
debug {"map #{term.inspect} to #{value.inspect}"}
|
396
|
-
iri_to_term.delete(@mappings[term].to_s) if @mappings[term]
|
397
|
-
@mappings[term] = value
|
398
|
-
@options[:prefixes][term_sym] = value if @options.has_key?(:prefixes)
|
399
|
-
iri_to_term[value.to_s] = term
|
400
|
-
end
|
401
|
-
|
402
|
-
##
|
403
|
-
# Reverse term mapping, typically used for finding aliases for keys.
|
404
|
-
#
|
405
|
-
# Returns either the original value, or a mapping for this value.
|
406
|
-
#
|
407
|
-
# @example
|
408
|
-
# {"@context": {"id": "@id"}, "@id": "foo"} => {"id": "foo"}
|
409
|
-
#
|
410
|
-
# @param [RDF::URI, String] value
|
411
|
-
# @return [String]
|
412
|
-
def alias(value)
|
413
|
-
iri_to_term.fetch(value, value)
|
414
|
-
end
|
415
|
-
|
416
|
-
##
|
417
|
-
# Retrieve term coercion
|
418
|
-
#
|
419
|
-
# @param [String] property in unexpanded form
|
420
|
-
#
|
421
|
-
# @return [RDF::URI, '@id']
|
422
|
-
def coerce(property)
|
423
|
-
# Map property, if it's not an RDF::Value
|
424
|
-
# @type is always is an IRI
|
425
|
-
return '@id' if [RDF.type, '@type'].include?(property)
|
426
|
-
@coercions.fetch(property, nil)
|
427
|
-
end
|
428
|
-
|
429
|
-
##
|
430
|
-
# Set term coercion
|
431
|
-
#
|
432
|
-
# @param [String] property in unexpanded form
|
433
|
-
# @param [RDF::URI, '@id'] value
|
434
|
-
#
|
435
|
-
# @return [RDF::URI, '@id']
|
436
|
-
def set_coerce(property, value)
|
437
|
-
debug {"coerce #{property.inspect} to #{value.inspect}"} unless @coercions[property.to_s] == value
|
438
|
-
if value
|
439
|
-
@coercions[property] = value
|
440
|
-
else
|
441
|
-
@coercions.delete(property)
|
442
|
-
end
|
443
|
-
end
|
444
|
-
|
445
|
-
##
|
446
|
-
# Retrieve container mapping, add it if `value` is provided
|
447
|
-
#
|
448
|
-
# @param [String] property in unexpanded form
|
449
|
-
# @return [String]
|
450
|
-
def container(property)
|
451
|
-
return '@set' if property == '@graph'
|
452
|
-
@containers.fetch(property.to_s, nil)
|
453
|
-
end
|
454
|
-
|
455
|
-
##
|
456
|
-
# Set container mapping
|
457
|
-
#
|
458
|
-
# @param [String] property
|
459
|
-
# @param [String] value one of @list, @set or nil
|
460
|
-
# @return [Boolean]
|
461
|
-
def set_container(property, value)
|
462
|
-
return if @containers[property.to_s] == value
|
463
|
-
debug {"coerce #{property.inspect} to #{value.inspect}"}
|
464
|
-
if value
|
465
|
-
@containers[property.to_s] = value
|
466
|
-
else
|
467
|
-
@containers.delete(value)
|
468
|
-
end
|
469
|
-
end
|
470
|
-
|
471
|
-
##
|
472
|
-
# Retrieve the language associated with a property, or the default language otherwise
|
473
|
-
# @return [String]
|
474
|
-
def language(property)
|
475
|
-
@languages.fetch(property.to_s, @default_language) if !coerce(property)
|
476
|
-
end
|
477
|
-
|
478
|
-
##
|
479
|
-
# Set language mapping
|
480
|
-
#
|
481
|
-
# @param [String] property
|
482
|
-
# @param [String] value
|
483
|
-
# @return [String]
|
484
|
-
def set_language(property, value)
|
485
|
-
# Use false for nil language
|
486
|
-
@languages[property.to_s] = value ? value : false
|
487
|
-
end
|
488
|
-
|
489
|
-
##
|
490
|
-
# Determine if `term` is a suitable term.
|
491
|
-
# Term may be any valid JSON string.
|
492
|
-
#
|
493
|
-
# @param [String] term
|
494
|
-
# @return [Boolean]
|
495
|
-
def term_valid?(term)
|
496
|
-
term.is_a?(String)
|
497
|
-
end
|
498
|
-
|
499
|
-
##
|
500
|
-
# Expand an IRI. Relative IRIs are expanded against any document base.
|
501
|
-
#
|
502
|
-
# @param [String] iri
|
503
|
-
# A keyword, term, prefix:suffix or possibly relative IRI
|
504
|
-
# @param [Hash{Symbol => Object}] options
|
505
|
-
# @option options [:subject, :predicate, :type] position
|
506
|
-
# Useful when determining how to serialize.
|
507
|
-
# @option options [RDF::URI] base (self.base)
|
508
|
-
# Base IRI to use when expanding relative IRIs.
|
509
|
-
# @option options [Array<String>] path ([])
|
510
|
-
# Array of looked up iris, used to find cycles
|
511
|
-
# @option options [BlankNodeNamer] namer
|
512
|
-
# Blank Node namer to use for renaming Blank Nodes
|
513
|
-
#
|
514
|
-
# @return [RDF::Term, String, Array<RDF::URI>]
|
515
|
-
# IRI or String, if it's a keyword, or array of IRI, if it matches
|
516
|
-
# a property generator
|
517
|
-
# @raise [RDF::ReaderError] if the iri cannot be expanded
|
518
|
-
# @see http://json-ld.org/spec/latest/json-ld-api/#iri-expansion
|
519
|
-
def expand_iri(iri, options = {})
|
520
|
-
return iri unless iri.is_a?(String)
|
521
|
-
|
522
|
-
prefix, suffix = iri.split(':', 2)
|
523
|
-
unless (m = mapping(iri)) == false
|
524
|
-
# It's an exact match
|
525
|
-
debug("expand_iri") {"match: #{iri.inspect} to #{m.inspect}"} unless options[:quiet]
|
526
|
-
return case m
|
527
|
-
when nil
|
528
|
-
nil
|
529
|
-
when Array
|
530
|
-
# Return array of IRIs, if it's a property generator
|
531
|
-
m.map {|mm| uri(mm.to_s, options[:namer])}
|
532
|
-
else
|
533
|
-
uri(m.to_s, options[:namer])
|
534
|
-
end
|
535
|
-
end
|
536
|
-
debug("expand_iri") {"prefix: #{prefix.inspect}, suffix: #{suffix.inspect}, vocab: #{vocab.inspect}"} unless options[:quiet]
|
537
|
-
base = [:subject, :type].include?(options[:position]) ? options.fetch(:base, self.base) : nil
|
538
|
-
prefix = prefix.to_s
|
539
|
-
case
|
540
|
-
when prefix == '_' && suffix then uri(bnode(suffix), options[:namer])
|
541
|
-
when iri.to_s[0,1] == "@" then iri
|
542
|
-
when suffix.to_s[0,2] == '//' then uri(iri)
|
543
|
-
when (mapping = mapping(prefix)) != false
|
544
|
-
debug("expand_iri") {"mapping: #{mapping(prefix).inspect}"} unless options[:quiet]
|
545
|
-
case mapping
|
546
|
-
when Array
|
547
|
-
# Return array of IRIs, if it's a property generator
|
548
|
-
mapping.map {|m| uri(m.to_s + suffix.to_s, options[:namer])}
|
549
|
-
else
|
550
|
-
uri(mapping.to_s + suffix.to_s, options[:namer])
|
551
|
-
end
|
552
|
-
when base then base.join(iri)
|
553
|
-
when vocab then uri("#{vocab}#{iri}")
|
554
|
-
else
|
555
|
-
# Otherwise, it must be an absolute IRI
|
556
|
-
u = uri(iri)
|
557
|
-
u if u.absolute? || [:subject, :type].include?(options[:position])
|
558
|
-
end
|
559
|
-
end
|
560
|
-
|
561
|
-
##
|
562
|
-
# Compacts an absolute IRI to the shortest matching term or compact IRI
|
563
|
-
#
|
564
|
-
# @param [RDF::URI] iri
|
565
|
-
# @param [Hash{Symbol => Object}] options ({})
|
566
|
-
# @option options [:subject, :predicate, :type] position
|
567
|
-
# Useful when determining how to serialize.
|
568
|
-
# @option options [Object] :value
|
569
|
-
# Value, used to select among various maps for the same IRI
|
570
|
-
# @option options [Boolean] :not_term (false)
|
571
|
-
# Don't return a term, but only a CURIE or IRI.
|
572
|
-
#
|
573
|
-
# @return [String] compacted form of IRI
|
574
|
-
# @see http://json-ld.org/spec/latest/json-ld-api/#iri-compaction
|
575
|
-
def compact_iri(iri, options = {})
|
576
|
-
depth(options) do
|
577
|
-
debug {"compact_iri(#{iri.inspect}, #{options.inspect})"}
|
578
|
-
|
579
|
-
value = options.fetch(:value, nil)
|
580
|
-
|
581
|
-
# Get a list of terms which map to iri
|
582
|
-
matched_terms = mappings.keys.select {|t| mapping(t).to_s == iri}
|
583
|
-
debug("compact_iri", "initial terms: #{matched_terms.inspect}")
|
584
|
-
|
585
|
-
# 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.
|
586
|
-
terms = {}
|
587
|
-
|
588
|
-
# If value is a @list select terms that match every item equivalently.
|
589
|
-
debug("compact_iri", "#{value.inspect} is a list? #{list?(value).inspect}") if value
|
590
|
-
if list?(value) && !index?(value)
|
591
|
-
list_terms = matched_terms.select {|t| container(t) == '@list'}
|
592
|
-
|
593
|
-
terms = list_terms.inject({}) do |memo, t|
|
594
|
-
memo[t] = term_rank(t, value)
|
595
|
-
memo
|
596
|
-
end unless list_terms.empty?
|
597
|
-
debug("term map") {"remove zero rank terms: #{terms.keys.select {|t| terms[t] == 0}}"} if terms.any? {|t,r| r == 0}
|
598
|
-
terms.delete_if {|t, r| r == 0}
|
599
|
-
end
|
600
|
-
|
601
|
-
# Otherwise, value is @value or a native type.
|
602
|
-
# Add a term rank for each term mapping to iri
|
603
|
-
# which does not have @container @list
|
604
|
-
if terms.empty?
|
605
|
-
non_list_terms = matched_terms.reject {|t| container(t) == '@list'}
|
606
|
-
|
607
|
-
# If value is a @list, exclude from term map those terms
|
608
|
-
# with @container @set
|
609
|
-
non_list_terms.reject {|t| container(t) == '@set'} if list?(value)
|
610
|
-
|
611
|
-
terms = non_list_terms.inject({}) do |memo, t|
|
612
|
-
memo[t] = term_rank(t, value)
|
613
|
-
memo
|
614
|
-
end unless non_list_terms.empty?
|
615
|
-
debug("term map") {"remove zero rank terms: #{terms.keys.select {|t| terms[t] == 0}}"} if terms.any? {|t,r| r == 0}
|
616
|
-
terms.delete_if {|t, r| r == 0}
|
617
|
-
end
|
618
|
-
|
619
|
-
# If we don't want terms, remove anything that's not a CURIE or IRI
|
620
|
-
terms.keep_if {|t, v| t.index(':') } if options.fetch(:not_term, false)
|
621
|
-
|
622
|
-
# Find terms having the greatest term match value
|
623
|
-
least_distance = terms.values.max
|
624
|
-
terms = terms.keys.select {|t| terms[t] == least_distance}
|
625
|
-
|
626
|
-
# If terms is empty, add a compact IRI representation of iri for each
|
627
|
-
# term in the active context which maps to an IRI which is a prefix for
|
628
|
-
# iri where the resulting compact IRI is not a term in the active
|
629
|
-
# context. The resulting compact IRI is the term associated with the
|
630
|
-
# partially matched IRI in the active context concatenated with a colon
|
631
|
-
# (:) character and the unmatched part of iri.
|
632
|
-
if terms.empty?
|
633
|
-
debug("curies") {"mappings: #{mappings.inspect}"}
|
634
|
-
curies = mappings.keys.map do |k|
|
635
|
-
debug("curies[#{k}]") {"#{mapping(k).inspect}"}
|
636
|
-
#debug("curies[#{k}]") {"#{(mapping(k).to_s.length > 0).inspect}, #{iri.to_s.index(mapping(k).to_s)}"}
|
637
|
-
iri.to_s.sub(mapping(k).to_s, "#{k}:") if
|
638
|
-
mapping(k).to_s.length > 0 &&
|
639
|
-
iri.to_s.index(mapping(k).to_s) == 0 &&
|
640
|
-
iri.to_s != mapping(k).to_s
|
641
|
-
end.compact
|
642
|
-
|
643
|
-
debug("curies") do
|
644
|
-
curies.map do |c|
|
645
|
-
"#{c}: " +
|
646
|
-
"container: #{container(c).inspect}, " +
|
647
|
-
"coerce: #{coerce(c).inspect}, " +
|
648
|
-
"lang: #{language(c).inspect}"
|
649
|
-
end.inspect
|
650
|
-
end
|
651
|
-
|
652
|
-
terms = curies.select do |curie|
|
653
|
-
(options[:position] != :predicate || container(curie) != '@list') &&
|
654
|
-
coerce(curie).nil? &&
|
655
|
-
language(curie) == default_language
|
656
|
-
end
|
657
|
-
|
658
|
-
debug("curies") {"selected #{terms.inspect}"}
|
659
|
-
end
|
660
|
-
|
661
|
-
# 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.
|
662
|
-
# Don't use vocab, if the result would collide with a term
|
663
|
-
if vocab && terms.empty? && iri.to_s.index(vocab) == 0 &&
|
664
|
-
!mapping(iri.to_s.sub(vocab, '')) &&
|
665
|
-
[:predicate, :type].include?(options[:position])
|
666
|
-
terms << iri.to_s.sub(vocab, '')
|
667
|
-
debug("vocab") {"vocab: #{vocab}, rel: #{terms.first}"}
|
668
|
-
end
|
669
|
-
|
670
|
-
# If we still don't have any terms and we're using standard_prefixes,
|
671
|
-
# try those, and add to mapping
|
672
|
-
if terms.empty? && @options[:standard_prefixes]
|
673
|
-
terms = RDF::Vocabulary.
|
674
|
-
select {|v| iri.index(v.to_uri.to_s) == 0}.
|
675
|
-
map do |v|
|
676
|
-
prefix = v.__name__.to_s.split('::').last.downcase
|
677
|
-
set_mapping(prefix, v.to_uri.to_s)
|
678
|
-
iri.sub(v.to_uri.to_s, "#{prefix}:").sub(/:$/, '')
|
679
|
-
end
|
680
|
-
debug("curies") {"using standard prefies: #{terms.inspect}"}
|
681
|
-
end
|
682
|
-
|
683
|
-
if terms.empty?
|
684
|
-
# If there is a mapping from the complete IRI to null, return null,
|
685
|
-
# otherwise, return the complete IRI.
|
686
|
-
if mappings.has_key?(iri.to_s) && !mapping(iri)
|
687
|
-
debug("iri") {"use nil IRI mapping"}
|
688
|
-
terms << nil
|
689
|
-
else
|
690
|
-
terms << iri.to_s
|
691
|
-
end
|
692
|
-
end
|
693
|
-
|
694
|
-
# Get the first term based on distance and lexecographical order
|
695
|
-
# Prefer terms that don't have @container @set over other terms, unless as set is true
|
696
|
-
terms = terms.sort do |a, b|
|
697
|
-
debug("term sort") {"c(a): #{container(a).inspect}, c(b): #{container(b)}"}
|
698
|
-
if a.to_s.length == b.to_s.length
|
699
|
-
a.to_s <=> b.to_s
|
700
|
-
else
|
701
|
-
a.to_s.length <=> b.to_s.length
|
702
|
-
end
|
703
|
-
end
|
704
|
-
debug("sorted terms") {terms.inspect}
|
705
|
-
result = terms.first
|
706
|
-
|
707
|
-
debug {"=> #{result.inspect}"}
|
708
|
-
result
|
709
|
-
end
|
710
|
-
end
|
711
|
-
|
712
|
-
##
|
713
|
-
# Expand a value from compacted to expanded form making the context
|
714
|
-
# unnecessary. This method is used as part of more general expansion
|
715
|
-
# and operates on RHS values, using a supplied key to determine @type and
|
716
|
-
# @container coercion rules.
|
717
|
-
#
|
718
|
-
# @param [String] property
|
719
|
-
# Associated property used to find coercion rules
|
720
|
-
# @param [Hash, String] value
|
721
|
-
# Value (literal or IRI) to be expanded
|
722
|
-
# @param [Hash{Symbol => Object}] options
|
723
|
-
# @option options [Boolean] :useNativeTypes (true) use native representations
|
724
|
-
# @option options [BlankNodeNamer] namer
|
725
|
-
# Blank Node namer to use for renaming Blank Nodes
|
726
|
-
#
|
727
|
-
# @return [Hash] Object representation of value
|
728
|
-
# @raise [RDF::ReaderError] if the iri cannot be expanded
|
729
|
-
# @see http://json-ld.org/spec/latest/json-ld-api/#value-expansion
|
730
|
-
def expand_value(property, value, options = {})
|
731
|
-
options = {:useNativeTypes => true}.merge(options)
|
732
|
-
depth(options) do
|
733
|
-
debug("expand_value") {"property: #{property.inspect}, value: #{value.inspect}, coerce: #{coerce(property).inspect}"}
|
734
|
-
|
735
|
-
value = if value.is_a?(RDF::Value)
|
736
|
-
value
|
737
|
-
elsif coerce(property) == '@id'
|
738
|
-
expand_iri(value, :position => :subject, :namer => options[:namer])
|
739
|
-
else
|
740
|
-
RDF::Literal(value)
|
741
|
-
end
|
742
|
-
debug("expand_value") {"normalized: #{value.inspect}"}
|
743
|
-
|
744
|
-
result = case value
|
745
|
-
when RDF::URI, RDF::Node
|
746
|
-
debug("URI | BNode") { value.to_s }
|
747
|
-
{'@id' => value.to_s}
|
748
|
-
when RDF::Literal
|
749
|
-
debug("Literal") {"datatype: #{value.datatype.inspect}"}
|
750
|
-
res = Hash.ordered
|
751
|
-
if options[:useNativeTypes] && [RDF::XSD.boolean, RDF::XSD.integer, RDF::XSD.double].include?(value.datatype)
|
752
|
-
res['@value'] = value.object
|
753
|
-
res['@type'] = uri(coerce(property), options[:namer]) if coerce(property)
|
754
|
-
else
|
755
|
-
value.canonicalize! if value.datatype == RDF::XSD.double
|
756
|
-
res['@value'] = value.to_s
|
757
|
-
if coerce(property)
|
758
|
-
res['@type'] = uri(coerce(property), options[:namer]).to_s
|
759
|
-
elsif value.has_datatype?
|
760
|
-
res['@type'] = uri(value.datatype, options[:namer]).to_s
|
761
|
-
elsif value.has_language? || language(property)
|
762
|
-
res['@language'] = (value.language || language(property)).to_s
|
763
|
-
end
|
764
|
-
end
|
765
|
-
res
|
766
|
-
end
|
767
|
-
|
768
|
-
debug {"=> #{result.inspect}"}
|
769
|
-
result
|
770
|
-
end
|
771
|
-
end
|
772
|
-
|
773
|
-
##
|
774
|
-
# Compact a value
|
775
|
-
#
|
776
|
-
# @param [String] property
|
777
|
-
# Associated property used to find coercion rules
|
778
|
-
# @param [Hash] value
|
779
|
-
# Value (literal or IRI), in full object representation, to be compacted
|
780
|
-
# @param [Hash{Symbol => Object}] options
|
781
|
-
#
|
782
|
-
# @return [Hash] Object representation of value
|
783
|
-
# @raise [ProcessingError] if the iri cannot be expanded
|
784
|
-
# @see http://json-ld.org/spec/latest/json-ld-api/#value-compaction
|
785
|
-
# FIXME: revisit the specification version of this.
|
786
|
-
def compact_value(property, value, options = {})
|
787
|
-
raise ProcessingError::Lossy, "attempt to compact a non-object value: #{value.inspect}" unless value.is_a?(Hash)
|
788
|
-
|
789
|
-
depth(options) do
|
790
|
-
debug("compact_value") {"property: #{property.inspect}, value: #{value.inspect}, coerce: #{coerce(property).inspect}"}
|
791
|
-
|
792
|
-
# Remove @index if property has annotation
|
793
|
-
value.delete('@index') if container(property) == '@index'
|
794
|
-
|
795
|
-
result = case
|
796
|
-
when value.has_key?('@index')
|
797
|
-
# Don't compact the value
|
798
|
-
debug {" (@index without container @index)"}
|
799
|
-
value
|
800
|
-
when coerce(property) == '@id' && value.has_key?('@id')
|
801
|
-
# Compact an @id coercion
|
802
|
-
debug {" (@id & coerce)"}
|
803
|
-
compact_iri(value['@id'], :position => :subject)
|
804
|
-
when value['@type'] && expand_iri(value['@type'], :position => :type) == coerce(property)
|
805
|
-
# Compact common datatype
|
806
|
-
debug {" (@type & coerce) == #{coerce(property)}"}
|
807
|
-
value['@value']
|
808
|
-
when value.has_key?('@id')
|
809
|
-
# Compact an IRI
|
810
|
-
value[self.alias('@id')] = compact_iri(value['@id'], :position => :subject)
|
811
|
-
debug {" (#{self.alias('@id')} => #{value['@id']})"}
|
812
|
-
value
|
813
|
-
when value['@language'] && (value['@language'] == language(property) || container(property) == '@language')
|
814
|
-
# Compact language
|
815
|
-
debug {" (@language) == #{language(property).inspect}"}
|
816
|
-
value['@value']
|
817
|
-
when !value.fetch('@value', "").is_a?(String)
|
818
|
-
# Compact simple literal to string
|
819
|
-
debug {" (@value not string)"}
|
820
|
-
value['@value']
|
821
|
-
when value['@value'] && !value['@language'] && !value['@type'] && !coerce(property) && !default_language
|
822
|
-
# Compact simple literal to string
|
823
|
-
debug {" (@value && !@language && !@type && !coerce && !language)"}
|
824
|
-
value['@value']
|
825
|
-
when value['@value'] && !value['@language'] && !value['@type'] && !coerce(property) && !language(property)
|
826
|
-
# Compact simple literal to string
|
827
|
-
debug {" (@value && !@language && !@type && !coerce && language(property).false)"}
|
828
|
-
value['@value']
|
829
|
-
when value['@type']
|
830
|
-
# Compact datatype
|
831
|
-
debug {" (@type)"}
|
832
|
-
value[self.alias('@type')] = compact_iri(value['@type'], :position => :type)
|
833
|
-
value
|
834
|
-
else
|
835
|
-
# Otherwise, use original value
|
836
|
-
debug {" (no change)"}
|
837
|
-
value
|
838
|
-
end
|
839
|
-
|
840
|
-
# If the result is an object, tranform keys using any term keyword aliases
|
841
|
-
if result.is_a?(Hash) && result.keys.any? {|k| self.alias(k) != k}
|
842
|
-
debug {" (map to key aliases)"}
|
843
|
-
new_element = {}
|
844
|
-
result.each do |k, v|
|
845
|
-
new_element[self.alias(k)] = v
|
846
|
-
end
|
847
|
-
result = new_element
|
848
|
-
end
|
849
|
-
|
850
|
-
debug {"=> #{result.inspect}"}
|
851
|
-
result
|
852
|
-
end
|
853
|
-
end
|
854
|
-
|
855
|
-
def inspect
|
856
|
-
v = %w([EvaluationContext)
|
857
|
-
v << "def_language=#{default_language}"
|
858
|
-
v << "languages[#{languages.keys.length}]=#{languages}"
|
859
|
-
v << "mappings[#{mappings.keys.length}]=#{mappings}"
|
860
|
-
v << "coercions[#{coercions.keys.length}]=#{coercions}"
|
861
|
-
v << "containers[#{containers.length}]=#{containers}"
|
862
|
-
v.join(", ") + "]"
|
863
|
-
end
|
864
|
-
|
865
|
-
def dup
|
866
|
-
# Also duplicate mappings, coerce and list
|
867
|
-
that = self
|
868
|
-
ec = super
|
869
|
-
ec.instance_eval do
|
870
|
-
@mappings = that.mappings.dup
|
871
|
-
@coerceions = that.coercions.dup
|
872
|
-
@containers = that.containers.dup
|
873
|
-
@languages = that.languages.dup
|
874
|
-
@default_language = that.default_language
|
875
|
-
@options = that.options
|
876
|
-
@iri_to_term = that.iri_to_term.dup
|
877
|
-
@iri_to_curie = that.iri_to_curie.dup
|
878
|
-
end
|
879
|
-
ec
|
880
|
-
end
|
881
|
-
|
882
|
-
private
|
883
|
-
|
884
|
-
def uri(value, namer = nil)
|
885
|
-
case value.to_s
|
886
|
-
when /^_:(.*)$/
|
887
|
-
# Map BlankNodes if a namer is given
|
888
|
-
debug "uri(bnode)#{value}: #{$1}"
|
889
|
-
bnode(namer ? namer.get_sym($1) : $1)
|
890
|
-
else
|
891
|
-
value = RDF::URI.new(value)
|
892
|
-
value.validate! if @options[:validate]
|
893
|
-
value.canonicalize! if @options[:canonicalize]
|
894
|
-
value = RDF::URI.intern(value) if @options[:intern]
|
895
|
-
value
|
896
|
-
end
|
897
|
-
end
|
898
|
-
|
899
|
-
# Keep track of allocated BNodes
|
900
|
-
#
|
901
|
-
# Don't actually use the name provided, to prevent name alias issues.
|
902
|
-
# @return [RDF::Node]
|
903
|
-
def bnode(value = nil)
|
904
|
-
@@bnode_cache ||= {}
|
905
|
-
@@bnode_cache[value.to_s] ||= RDF::Node.new(value)
|
906
|
-
end
|
907
|
-
|
908
|
-
##
|
909
|
-
# Get a "match value" given a term and a value. The value
|
910
|
-
# is lowest when the relative match between the term and the value
|
911
|
-
# is closest.
|
912
|
-
#
|
913
|
-
# @param [String] term
|
914
|
-
# @param [Object] value
|
915
|
-
# @return [Integer]
|
916
|
-
def term_rank(term, value)
|
917
|
-
default_term = !coerce(term) && !languages.has_key?(term)
|
918
|
-
debug("term rank") {
|
919
|
-
"term: #{term.inspect}, " +
|
920
|
-
"value: #{value.inspect}, " +
|
921
|
-
"coerce: #{coerce(term).inspect}, " +
|
922
|
-
"lang: #{languages.fetch(term, nil).inspect}/#{language(term).inspect} " +
|
923
|
-
"default_term: #{default_term.inspect}"
|
924
|
-
}
|
925
|
-
|
926
|
-
# value is null
|
927
|
-
rank = if value.nil?
|
928
|
-
debug("term rank") { "null value: 3"}
|
929
|
-
3
|
930
|
-
elsif list?(value)
|
931
|
-
if value['@list'].empty?
|
932
|
-
# If the @list property is an empty array, if term has @container set to @list, term rank is 1, otherwise 0.
|
933
|
-
debug("term rank") { "empty list"}
|
934
|
-
container(term) == '@list' ? 1 : 0
|
935
|
-
else
|
936
|
-
debug("term rank") { "non-empty list"}
|
937
|
-
# Otherwise, return the most specific term, for which the term has some match against every value.
|
938
|
-
depth {value['@list'].map {|v| term_rank(term, v)}}.min
|
939
|
-
end
|
940
|
-
elsif value?(value)
|
941
|
-
val_type = value.fetch('@type', nil)
|
942
|
-
val_lang = value['@language'] || false if value.has_key?('@language')
|
943
|
-
debug("term rank") {"@val_type: #{val_type.inspect}, val_lang: #{val_lang.inspect}"}
|
944
|
-
if val_type
|
945
|
-
debug("term rank") { "typed value"}
|
946
|
-
coerce(term) == val_type ? 3 : (default_term ? 1 : 0)
|
947
|
-
elsif !value['@value'].is_a?(String)
|
948
|
-
debug("term rank") { "native value"}
|
949
|
-
default_term ? 2 : 1
|
950
|
-
elsif val_lang.nil?
|
951
|
-
debug("val_lang.nil") {"#{language(term).inspect} && #{coerce(term).inspect}"}
|
952
|
-
if language(term) == false || (default_term && default_language.nil?)
|
953
|
-
# Value has no language, and there is no default language and the term has no language
|
954
|
-
3
|
955
|
-
elsif default_term
|
956
|
-
# The term has no language (or type), but it's different than the default
|
957
|
-
2
|
958
|
-
else
|
959
|
-
0
|
960
|
-
end
|
961
|
-
else
|
962
|
-
debug("val_lang") {"#{language(term).inspect} && #{coerce(term).inspect}"}
|
963
|
-
if val_lang && container(term) == '@language'
|
964
|
-
3
|
965
|
-
elsif val_lang == language(term) || (default_term && default_language == val_lang)
|
966
|
-
2
|
967
|
-
elsif default_term && container(term) == '@set'
|
968
|
-
2 # Choose a set term before a non-set term, if there's a language
|
969
|
-
elsif default_term
|
970
|
-
1
|
971
|
-
else
|
972
|
-
0
|
973
|
-
end
|
974
|
-
end
|
975
|
-
else # node definition/reference
|
976
|
-
debug("node dev/ref")
|
977
|
-
coerce(term) == '@id' ? 3 : (default_term ? 1 : 0)
|
978
|
-
end
|
979
|
-
|
980
|
-
debug(" =>") {rank.inspect}
|
981
|
-
rank
|
982
|
-
end
|
983
|
-
end
|
984
|
-
end
|