json-ld 0.9.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/{README.markdown → README.md} +15 -3
  2. data/VERSION +1 -1
  3. data/lib/json/ld.rb +50 -87
  4. data/lib/json/ld/api.rb +85 -96
  5. data/lib/json/ld/compact.rb +103 -170
  6. data/lib/json/ld/context.rb +1137 -0
  7. data/lib/json/ld/expand.rb +212 -171
  8. data/lib/json/ld/extensions.rb +17 -1
  9. data/lib/json/ld/flatten.rb +145 -78
  10. data/lib/json/ld/frame.rb +1 -1
  11. data/lib/json/ld/from_rdf.rb +73 -103
  12. data/lib/json/ld/reader.rb +3 -1
  13. data/lib/json/ld/resource.rb +3 -3
  14. data/lib/json/ld/to_rdf.rb +98 -109
  15. data/lib/json/ld/utils.rb +54 -4
  16. data/lib/json/ld/writer.rb +5 -5
  17. data/spec/api_spec.rb +3 -28
  18. data/spec/compact_spec.rb +76 -113
  19. data/spec/{evaluation_context_spec.rb → context_spec.rb} +307 -563
  20. data/spec/expand_spec.rb +163 -187
  21. data/spec/flatten_spec.rb +119 -114
  22. data/spec/frame_spec.rb +5 -5
  23. data/spec/from_rdf_spec.rb +44 -24
  24. data/spec/suite_compact_spec.rb +11 -8
  25. data/spec/suite_error_expand_spec.rb +23 -0
  26. data/spec/suite_expand_spec.rb +3 -7
  27. data/spec/suite_flatten_spec.rb +3 -3
  28. data/spec/suite_frame_spec.rb +6 -6
  29. data/spec/suite_from_rdf_spec.rb +3 -3
  30. data/spec/suite_helper.rb +13 -6
  31. data/spec/suite_to_rdf_spec.rb +16 -10
  32. data/spec/test-files/test-1-rdf.ttl +4 -3
  33. data/spec/test-files/test-3-rdf.ttl +2 -1
  34. data/spec/test-files/test-4-compacted.json +1 -1
  35. data/spec/test-files/test-5-rdf.ttl +3 -2
  36. data/spec/test-files/test-6-rdf.ttl +3 -2
  37. data/spec/test-files/test-7-compacted.json +3 -3
  38. data/spec/test-files/test-7-expanded.json +3 -3
  39. data/spec/test-files/test-7-rdf.ttl +7 -6
  40. data/spec/test-files/test-9-compacted.json +1 -1
  41. data/spec/to_rdf_spec.rb +67 -75
  42. data/spec/writer_spec.rb +2 -0
  43. metadata +36 -24
  44. checksums.yaml +0 -15
  45. data/lib/json/ld/evaluation_context.rb +0 -984
@@ -31,6 +31,8 @@ describe JSON::LD::Writer do
31
31
  serialize(input).should produce([{
32
32
  '@id' => "http://a/b",
33
33
  "http://a/c" => [{"@id" => "http://a/d"}]
34
+ }, {
35
+ "@id" => "http://a/d"
34
36
  }], @debug)
35
37
  end
36
38
 
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.9.1
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-04-07 00:00:00.000000000 Z
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
- requirements:
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.markdown
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/evaluation_context.rb
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/evaluation_context_spec.rb
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.8.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: 2.0.3
319
+ rubygems_version: 1.8.25
309
320
  signing_key:
310
- specification_version: 4
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/evaluation_context_spec.rb
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