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.
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