json-ld 1.1.7 → 1.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b6c779ef87a832a22299e041f6247302d291701d
4
- data.tar.gz: 1dea55d7a1187607566c39865ed76ca4dbc8131a
3
+ metadata.gz: 1f6790b14a0a5f9c477e63ad9b8788b03be1eabe
4
+ data.tar.gz: 8fc23868782eb98f4c375607ff6bf01813a5aa78
5
5
  SHA512:
6
- metadata.gz: 2e8560d7338e1d891bf5dc2d0959d1a767b5fa1e73a89be5533f5b064dbc135187a755f0f736683945e2ea52fe89fece40bed2f24e5dd075cd10790634c4a7a8
7
- data.tar.gz: 987d781688176b608e3f0d70f18e50936376096857176293f14b995d061982ec5c9bc1e64e75fdbca7e797d5a3069ffe6002658e5c623205b605d796430a770f
6
+ metadata.gz: 7134666ce6f5e0c47148c6a032e1fb13be5c272abd5db50b1a84b7925857b7a86e3f3444718cb2b0d7717eed49a4d24f70bb6a3beaa3da6b394ad68aab1c6140
7
+ data.tar.gz: 88f96434d3c7b342e7be136c3b9b20ded52c93f4d8234af446e164336decc7a6b9dbd99f59d362619a8fe6f95a7855adbc8feacb929c99601e41a415a7739ded
data/README.md CHANGED
@@ -13,6 +13,12 @@ JSON::LD can now be used to create a _context_ from an RDFS/OWL definition, and
13
13
 
14
14
  Install with `gem install json-ld`
15
15
 
16
+ ### JSON-LD Streaming Profile
17
+ This gem implements an optimized streaming writer used for generating JSON-LD from large repositories. Such documents result in the JSON-LD Streaming Profile:
18
+
19
+ * Each statement written as a separate node in expanded/flattened form.
20
+ * RDF Lists are written as separate nodes using `rdf:first` and `rdf:rest` properties.
21
+
16
22
  ## Examples
17
23
 
18
24
  require 'rubygems'
@@ -165,7 +171,7 @@ Install with `gem install json-ld`
165
171
  graph = RDF::Graph.new << JSON::LD::API.toRdf(input)
166
172
 
167
173
  require 'rdf/turtle'
168
- graph.dump(:ttl, :prefixes => {:foaf => "http://xmlns.com/foaf/0.1/"})
174
+ graph.dump(:ttl, prefixes: {foaf: "http://xmlns.com/foaf/0.1/"})
169
175
  @prefix foaf: <http://xmlns.com/foaf/0.1/> .
170
176
 
171
177
  <http://example.org/people#joebob> a foaf:Person;
@@ -213,8 +219,8 @@ Install with `gem install json-ld`
213
219
  ## RDF Reader and Writer
214
220
  {JSON::LD} also acts as a normal RDF reader and writer, using the standard RDF.rb reader/writer interfaces:
215
221
 
216
- graph = RDF::Graph.load("etc/doap.jsonld", :format => :jsonld)
217
- graph.dump(:jsonld, :standard_prefixes => true)
222
+ graph = RDF::Graph.load("etc/doap.jsonld", format: :jsonld)
223
+ graph.dump(:jsonld, standard_prefixes: true)
218
224
 
219
225
  `RDF::GRAPH#dump` can also take a `:context` option to use a separately defined context
220
226
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.7
1
+ 1.1.8
data/bin/jsonld CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'rubygems'
3
- $:.unshift(File.expand_path("../../lib", __FILE__))
4
3
  begin
5
4
  require 'linkeddata'
6
5
  rescue LoadError
7
6
  end
7
+ $:.unshift(File.expand_path("../../lib", __FILE__))
8
8
  require 'json/ld'
9
9
  require 'getoptlong'
10
10
  require 'open-uri'
@@ -17,42 +17,49 @@ def run(input, options)
17
17
  options[:format] = :jsonld if options[:compact] || options[:frame]
18
18
 
19
19
  # If input format is not JSON-LD, transform input to JSON-LD first
20
- if options[:format] == :jsonld && options[:input_format] != :jsonld
21
- r = reader_class.new(input, options[:parser_options])
22
- g = RDF::Repository.new << r
23
- input = JSON::LD::API.fromRdf(g)
20
+ reader = if options[:input_format] != :jsonld
21
+ reader_class.new(input, options[:parser_options])
24
22
  end
25
23
 
26
24
  prefixes = {}
27
25
  start = Time.new
28
26
  if options[:expand]
29
- options = options.merge(:expandContext => options.delete(:context)) if options.has_key?(:context)
27
+ options = options.merge(expandContext: options.delete(:context)) if options.has_key?(:context)
28
+ input = JSON::LD::API.fromRdf(reader) if reader
30
29
  output = JSON::LD::API.expand(input, options)
31
30
  secs = Time.new - start
32
31
  options[:output].puts output.to_json(JSON::LD::JSON_STATE)
33
32
  STDERR.puts "Expanded in #{secs} seconds." unless options[:quiet]
34
33
  elsif options[:compact]
34
+ input = JSON::LD::API.fromRdf(reader) if reader
35
35
  output = JSON::LD::API.compact(input, options[:context], options)
36
36
  secs = Time.new - start
37
37
  options[:output].puts output.to_json(JSON::LD::JSON_STATE)
38
38
  STDERR.puts "Compacted in #{secs} seconds." unless options[:quiet]
39
39
  elsif options[:flatten]
40
+ input = JSON::LD::API.fromRdf(reader) if reader
40
41
  output = JSON::LD::API.flatten(input, options[:context], options)
41
42
  secs = Time.new - start
42
43
  options[:output].puts output.to_json(JSON::LD::JSON_STATE)
43
44
  STDERR.puts "Flattened in #{secs} seconds." unless options[:quiet]
44
45
  elsif options[:frame]
46
+ input = JSON::LD::API.fromRdf(reader) if reader
45
47
  output = JSON::LD::API.frame(input, options[:frame], options)
46
48
  secs = Time.new - start
47
49
  options[:output].puts output.to_json(JSON::LD::JSON_STATE)
48
50
  STDERR.puts "Framed in #{secs} seconds." unless options[:quiet]
49
51
  else
50
- options = options.merge(:expandContext => options.delete(:context)) if options.has_key?(:context)
51
- g = JSON::LD::API.toRdf(input, options)
52
+ options = options.merge(expandContext: options.delete(:context)) if options.has_key?(:context)
53
+ parser_options = options[:parser_options].merge(standard_prefixes: true)
54
+ reader ||= JSON::LD::Reader.new(input, parser_options)
55
+ num = 0
56
+ RDF::Writer.for(options[:output_format]).new(options[:output], parser_options) do |w|
57
+ reader.each do |statement|
58
+ num += 1
59
+ w << statement
60
+ end
61
+ end
52
62
  secs = Time.new - start
53
- num = g.count
54
- parser_options = options[:parser_options].merge(:standard_prefixes => true)
55
- options[:output].puts g.dump(options[:output_format], parser_options)
56
63
  STDERR.puts "\nParsed #{num} statements in #{secs} seconds @ #{num/secs} statements/second." unless options[:quiet]
57
64
  end
58
65
  rescue
@@ -62,17 +69,18 @@ rescue
62
69
  end
63
70
 
64
71
  parser_options = {
65
- :base => nil,
66
- :progress => false,
67
- :validate => false,
68
- :strict => false,
72
+ base: nil,
73
+ progress: false,
74
+ validate: false,
75
+ stream: false,
76
+ strict: false,
69
77
  }
70
78
 
71
79
  options = {
72
- :parser_options => parser_options,
73
- :output => STDOUT,
74
- :output_format => :turtle,
75
- :input_format => :jsonld,
80
+ parser_options: parser_options,
81
+ output: STDOUT,
82
+ output_format: :jsonld,
83
+ input_format: :jsonld,
76
84
  }
77
85
  input = nil
78
86
 
@@ -89,8 +97,9 @@ OPT_ARGS = [
89
97
  ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT,"Output to the specified file path"],
90
98
  ["--parse-only", GetoptLong::NO_ARGUMENT, "Parse the document for well-formedness only"],
91
99
  ["--quiet", GetoptLong::NO_ARGUMENT, "Supress most output other than progress indicators"],
100
+ ["--stream", GetoptLong::NO_ARGUMENT, "Use Streaming reader/writer"],
92
101
  ["--uri", GetoptLong::REQUIRED_ARGUMENT,"URI to be used as the document base"],
93
- ["--verbose", GetoptLong::NO_ARGUMENT, "Detail on execution"],
102
+ ["--validate", GetoptLong::NO_ARGUMENT, "Validate while processing"],
94
103
  ["--help", "-?", GetoptLong::NO_ARGUMENT, "This message"]
95
104
  ]
96
105
  def usage
@@ -108,7 +117,6 @@ def usage
108
117
  exit(1)
109
118
  end
110
119
 
111
-
112
120
  opts = GetoptLong.new(*OPT_ARGS.map {|o| o[0..-2]})
113
121
 
114
122
  opts.each do |opt, arg|
@@ -125,18 +133,29 @@ opts.each do |opt, arg|
125
133
  when '--output' then options[:output] = File.open(arg, "w")
126
134
  when '--parse-only' then options[:parse_only] = true
127
135
  when '--quiet' then options[:quiet] = true
136
+ when '--stream' then parser_options[:stream] = true
128
137
  when '--uri' then parser_options[:base] = arg
129
- when '--verbose' then $verbose = true
138
+ when '--validate' then parser_options[:validate] = true
130
139
  when '--help' then usage
131
140
  end
132
141
  end
133
142
 
143
+ # Hack
144
+ options[:parser_options][:context] = options[:context] if parser_options[:stream]
145
+
146
+ if !(options.keys & [:expand, :compact, :flatten, :frame]).empty? &&
147
+ (parser_options[:stream] || options[:output_format] != :jsonld)
148
+ STDERR.puts "Incompatible options"
149
+ exit(1)
150
+ end
151
+
134
152
  if ARGV.empty?
135
153
  s = input ? input : $stdin.read
136
154
  run(StringIO.new(s), options)
137
155
  else
138
156
  ARGV.each do |file|
139
- run(file, options)
157
+ # Call with opened files
158
+ RDF::Util::File.open_file(file, options) {|f| run(f, options)}
140
159
  end
141
160
  end
142
161
  puts
@@ -25,7 +25,7 @@ module JSON
25
25
  require 'json/ld/format'
26
26
  require 'json/ld/utils'
27
27
  autoload :API, 'json/ld/api'
28
- autoload :Context, 'json/ld/context'
28
+ autoload :Context, 'json/ld/context'
29
29
  autoload :Normalize, 'json/ld/normalize'
30
30
  autoload :Reader, 'json/ld/reader'
31
31
  autoload :Resource, 'json/ld/resource'
@@ -77,11 +77,11 @@ module JSON
77
77
  NATIVE_DATATYPES = [RDF::XSD.integer.to_s, RDF::XSD.boolean.to_s, RDF::XSD.double.to_s]
78
78
 
79
79
  JSON_STATE = JSON::State.new(
80
- :indent => " ",
81
- :space => " ",
82
- :space_before => "",
83
- :object_nl => "\n",
84
- :array_nl => "\n"
80
+ indent: " ",
81
+ space: " ",
82
+ space_before: "",
83
+ object_nl: "\n",
84
+ array_nl: "\n"
85
85
  )
86
86
 
87
87
  def self.debug?; @debug; end
@@ -26,7 +26,7 @@ module JSON::LD
26
26
 
27
27
  # Options used for open_file
28
28
  OPEN_OPTS = {
29
- :headers => {"Accept" => "application/ld+json, application/json"}
29
+ headers: {"Accept" => "application/ld+json, application/json"}
30
30
  }
31
31
 
32
32
  # Current input
@@ -79,21 +79,32 @@ module JSON::LD
79
79
  # @yield [api]
80
80
  # @yieldparam [API]
81
81
  def initialize(input, context, options = {}, &block)
82
- @options = {:compactArrays => true}.merge(options)
82
+ @options = {compactArrays: true, rename_bnodes: true}.merge(options)
83
83
  @options[:validate] = true if @options[:processingMode] == "json-ld-1.0"
84
84
  @options[:documentLoader] ||= self.class.method(:documentLoader)
85
- options[:rename_bnodes] ||= true
86
- @namer = options[:unique_bnodes] ? BlankNodeUniqer.new : (options[:rename_bnodes] ? BlankNodeNamer.new("b") : BlankNodeMapper.new)
85
+ @namer = options[:unique_bnodes] ? BlankNodeUniqer.new : (@options[:rename_bnodes] ? BlankNodeNamer.new("b") : BlankNodeMapper.new)
86
+
87
+ # For context via Link header
88
+ context_ref = nil
89
+
87
90
  @value = case input
88
91
  when Array, Hash then input.dup
89
92
  when IO, StringIO
90
- @options = {:base => input.base_uri}.merge(@options) if input.respond_to?(:base_uri)
93
+ @options = {base: input.base_uri}.merge(@options) if input.respond_to?(:base_uri)
94
+
95
+ # if input impelements #links, attempt to get a contextUrl from that link
96
+ content_type = input.respond_to?(:content_type) ? input.content_type : "application/json"
97
+ context_ref = if content_type.start_with?('application/json') && input.respond_to?(:links)
98
+ link = input.links.find_link(%w(rel http://www.w3.org/ns/json-ld#context))
99
+ link.href if link
100
+ end
101
+
91
102
  JSON.parse(input.read)
92
103
  when String
93
104
  remote_doc = @options[:documentLoader].call(input, @options)
94
105
 
95
- @options = {:base => remote_doc.documentUrl}.merge(@options)
96
- context = context ? [context, remote_doc.contextUrl].compact : remote_doc.contextUrl
106
+ @options = {base: remote_doc.documentUrl}.merge(@options)
107
+ context_ref = remote_doc.contextUrl
97
108
 
98
109
  case remote_doc.document
99
110
  when String then JSON.parse(remote_doc.document)
@@ -103,6 +114,9 @@ module JSON::LD
103
114
 
104
115
  # Update calling context :base option, if not defined
105
116
  options[:base] ||= @options[:base] if @options[:base]
117
+
118
+ # If not provided, first use context from document, or from a Link header
119
+ context ||= (@value['@context'] if @value.is_a?(Hash)) || context_ref
106
120
  @context = Context.new(@options)
107
121
  @context = @context.parse(context) if context
108
122
 
@@ -177,13 +191,13 @@ module JSON::LD
177
191
  expanded = API.expand(input, options)
178
192
 
179
193
  API.new(expanded, context, options) do
180
- debug(".compact") {"expanded input: #{expanded.to_json(JSON_STATE)}"}
194
+ debug(".compact") {"expanded input: #{expanded.to_json(JSON_STATE) rescue 'malformed json'}"}
181
195
  result = compact(value, nil)
182
196
 
183
197
  # xxx) Add the given context to the output
184
198
  ctx = self.context.serialize
185
199
  if result.is_a?(Array)
186
- kwgraph = self.context.compact_iri('@graph', :vocab => true, :quiet => true)
200
+ kwgraph = self.context.compact_iri('@graph', vocab: true, quiet: true)
187
201
  result = result.empty? ? {} : {kwgraph => result}
188
202
  end
189
203
  result = ctx.merge(result) unless ctx.empty?
@@ -219,7 +233,7 @@ module JSON::LD
219
233
 
220
234
  # Initialize input using frame as context
221
235
  API.new(expanded_input, context, options) do
222
- debug(".flatten") {"expanded input: #{value.to_json(JSON_STATE)}"}
236
+ debug(".flatten") {"expanded input: #{value.to_json(JSON_STATE) rescue 'malformed json'}"}
223
237
 
224
238
  # Initialize node map to a JSON object consisting of a single member whose key is @default and whose value is an empty JSON object.
225
239
  node_map = {'@default' => {}}
@@ -242,7 +256,7 @@ module JSON::LD
242
256
  # Otherwise, return the result of compacting flattened according the Compaction algorithm passing context ensuring that the compaction result uses the @graph keyword (or its alias) at the top-level, even if the context is empty or if there is only one element to put in the @graph array. This ensures that the returned document has a deterministic structure.
243
257
  compacted = depth {compact(flattened, nil)}
244
258
  compacted = [compacted] unless compacted.is_a?(Array)
245
- kwgraph = self.context.compact_iri('@graph', :quiet => true)
259
+ kwgraph = self.context.compact_iri('@graph', quiet: true)
246
260
  flattened = self.context.serialize.merge(kwgraph => compacted)
247
261
  end
248
262
  end
@@ -284,10 +298,10 @@ module JSON::LD
284
298
  def self.frame(input, frame, options = {})
285
299
  result = nil
286
300
  framing_state = {
287
- :embed => true,
288
- :explicit => false,
289
- :omitDefault => false,
290
- :embeds => nil,
301
+ embed: true,
302
+ explicit: false,
303
+ omitDefault: false,
304
+ embeds: nil,
291
305
  }
292
306
  framing_state[:embed] = options[:embed] if options.has_key?(:embed)
293
307
  framing_state[:explicit] = options[:explicit] if options.has_key?(:explicit)
@@ -315,9 +329,9 @@ module JSON::LD
315
329
  # Initialize input using frame as context
316
330
  API.new(expanded_input, nil, options) do
317
331
  #debug(".frame") {"context from frame: #{context.inspect}"}
318
- debug(".frame") {"raw frame: #{frame.to_json(JSON_STATE)}"}
319
- debug(".frame") {"expanded frame: #{expanded_frame.to_json(JSON_STATE)}"}
320
- debug(".frame") {"expanded input: #{value.to_json(JSON_STATE)}"}
332
+ debug(".frame") {"raw frame: #{frame.to_json(JSON_STATE) rescue 'malformed json'}"}
333
+ debug(".frame") {"expanded frame: #{expanded_frame.to_json(JSON_STATE) rescue 'malformed json'}"}
334
+ debug(".frame") {"expanded input: #{value.to_json(JSON_STATE) rescue 'malformed json'}"}
321
335
 
322
336
  # Get framing nodes from expanded input, replacing Blank Node identifiers as necessary
323
337
  all_nodes = {}
@@ -327,11 +341,11 @@ module JSON::LD
327
341
  end
328
342
  @options[:debug] = old_dbg
329
343
  @node_map = all_nodes['@default']
330
- debug(".frame") {"node_map: #{@node_map.to_json(JSON_STATE)}"}
344
+ debug(".frame") {"node_map: #{@node_map.to_json(JSON_STATE) rescue 'malformed json'}"}
331
345
 
332
346
  result = []
333
347
  frame(framing_state, @node_map, (expanded_frame.first || {}), parent: result)
334
- debug(".frame") {"after frame: #{result.to_json(JSON_STATE)}"}
348
+ debug(".frame") {"after frame: #{result.to_json(JSON_STATE) rescue 'malformed json'}"}
335
349
 
336
350
  # Initalize context from frame
337
351
  @context = depth {@context.parse(frame['@context'])}
@@ -340,9 +354,9 @@ module JSON::LD
340
354
  compacted = [compacted] unless compacted.is_a?(Array)
341
355
 
342
356
  # Add the given context to the output
343
- kwgraph = context.compact_iri('@graph', :quiet => true)
357
+ kwgraph = context.compact_iri('@graph', quiet: true)
344
358
  result = context.serialize.merge({kwgraph => compacted})
345
- debug(".frame") {"after compact: #{result.to_json(JSON_STATE)}"}
359
+ debug(".frame") {"after compact: #{result.to_json(JSON_STATE) rescue 'malformed json'}"}
346
360
  result = cleanup_preserve(result)
347
361
  end
348
362
 
@@ -374,17 +388,17 @@ module JSON::LD
374
388
  end
375
389
 
376
390
  # Expand input to simplify processing
377
- expanded_input = API.expand(input, options.merge(:ordered => false))
391
+ expanded_input = API.expand(input, options.merge(ordered: false))
378
392
 
379
393
  API.new(expanded_input, nil, options) do
380
394
  # 1) Perform the Expansion Algorithm on the JSON-LD input.
381
395
  # This removes any existing context to allow the given context to be cleanly applied.
382
- debug(".toRdf") {"expanded input: #{expanded_input.to_json(JSON_STATE)}"}
396
+ debug(".toRdf") {"expanded input: #{expanded_input.to_json(JSON_STATE) rescue 'malformed json'}"}
383
397
 
384
398
  # Generate _nodeMap_
385
399
  node_map = {'@default' => {}}
386
400
  generate_node_map(expanded_input, node_map)
387
- debug(".toRdf") {"node map: #{node_map.to_json(JSON_STATE)}"}
401
+ debug(".toRdf") {"node map: #{node_map.to_json(JSON_STATE) rescue 'malformed json'}"}
388
402
 
389
403
  # Start generating statements
390
404
  node_map.each do |graph_name, graph|
@@ -439,7 +453,7 @@ module JSON::LD
439
453
  # @return [Array<Hash>]
440
454
  # The JSON-LD document in expanded form
441
455
  def self.fromRdf(input, options = {}, &block)
442
- options = {:useNativeTypes => false}.merge(options)
456
+ options = {useNativeTypes: false}.merge(options)
443
457
  result = nil
444
458
 
445
459
  API.new(nil, nil, options) do |api|
@@ -461,78 +475,39 @@ module JSON::LD
461
475
  # @yieldparam [RemoteDocument] remote_document
462
476
  # @raise [JsonLdError]
463
477
  def self.documentLoader(url, options = {})
464
- require 'net/http' unless defined?(Net::HTTP)
465
- remote_document = nil
466
- options[:headers] ||= OPEN_OPTS[:headers]
467
-
468
- url = url.to_s[5..-1] if url.to_s.start_with?("file:")
469
- case url.to_s
470
- when /^http/
471
- parsed_url = ::URI.parse(url.to_s)
472
- until remote_document do
473
- Net::HTTP::start(parsed_url.host, parsed_url.port,
474
- open_timeout: 60 * 1000,
475
- use_ssl: parsed_url.scheme == 'https',
476
- verify_mode: OpenSSL::SSL::VERIFY_NONE
477
- ) do |http|
478
- request = Net::HTTP::Get.new(parsed_url.request_uri, options[:headers])
479
- http.request(request) do |response|
480
- case response
481
- when Net::HTTPSuccess
482
- # found object
483
- content_type, ct_param = response.content_type.to_s.downcase.split(";")
484
-
485
- # If the passed input is a DOMString representing the IRI of a remote document, dereference it. If the retrieved document's content type is neither application/json, nor application/ld+json, nor any other media type using a +json suffix as defined in [RFC6839], reject the promise passing an loading document failed error.
486
- if content_type && options[:validate]
487
- main, sub = content_type.split("/")
488
- raise JSON::LD::JsonLdError::LoadingDocumentFailed, "content_type: #{content_type}" if
489
- main != 'application' ||
490
- sub !~ /^(.*\+)?json$/
491
- end
492
-
493
- remote_document = RemoteDocument.new(parsed_url.to_s, response.body)
494
-
495
- # If the input has been retrieved, the response has an HTTP Link Header [RFC5988] using the http://www.w3.org/ns/json-ld#context link relation and a content type of application/json or any media type with a +json suffix as defined in [RFC6839] except application/ld+json, update the active context using the Context Processing algorithm, passing the context referenced in the HTTP Link Header as local context. The HTTP Link Header is ignored for documents served as application/ld+json If multiple HTTP Link Headers using the http://www.w3.org/ns/json-ld#context link relation are found, the promise is rejected with a JsonLdError whose code is set to multiple context link headers and processing is terminated.
496
- unless content_type.to_s.start_with?("application/ld+json")
497
- links = response["link"].to_s.
498
- split(",").
499
- map(&:strip).
500
- select {|h| h =~ %r{rel=\"http://www.w3.org/ns/json-ld#context\"}}
501
- case links.length
502
- when 0 then #nothing to do
503
- when 1
504
- remote_document.contextUrl = links.first.match(/<([^>]*)>/) && $1
505
- else
506
- raise JSON::LD::JsonLdError::MultipleContextLinkHeaders,
507
- "expected at most 1 Link header with rel=jsonld:context, got #{links.length}"
508
- end
509
- end
510
-
511
- return block_given? ? yield(remote_document) : remote_document
512
- when Net::HTTPRedirection
513
- # Follow redirection
514
- parsed_url = ::URI.parse(response["Location"])
515
- else
516
- raise JSON::LD::JsonLdError::LoadingDocumentFailed, "<#{parsed_url}>: #{response.msg}(#{response.code})"
517
- end
518
- end
519
- end
478
+ options = OPEN_OPTS.merge(options)
479
+ RDF::Util::File.open_file(url, options) do |remote_doc|
480
+ content_type = remote_doc.content_type if remote_doc.respond_to?(:content_type)
481
+ # If the passed input is a DOMString representing the IRI of a remote document, dereference it. If the retrieved document's content type is neither application/json, nor application/ld+json, nor any other media type using a +json suffix as defined in [RFC6839], reject the promise passing an loading document failed error.
482
+ if content_type && options[:validate]
483
+ main, sub = content_type.split("/")
484
+ raise JSON::LD::JsonLdError::LoadingDocumentFailed, "content_type: #{content_type}" if
485
+ main != 'application' ||
486
+ sub !~ /^(.*\+)?json$/
520
487
  end
521
- else
522
- # Use regular open
523
- RDF::Util::File.open_file(url, options) do |f|
524
- remote_document = RemoteDocument.new(url, f.read)
525
- content_type, ct_param = f.content_type.to_s.downcase.split(";") if f.respond_to?(:content_type)
526
- if content_type && options[:validate]
527
- main, sub = content_type.split("/")
528
- raise JSON::LD::JsonLdError::LoadingDocumentFailed, "content_type: #{content_type}" if
529
- main != 'application' ||
530
- sub !~ /^(.*\+)?json$/
488
+
489
+ # If the input has been retrieved, the response has an HTTP Link Header [RFC5988] using the http://www.w3.org/ns/json-ld#context link relation and a content type of application/json or any media type with a +json suffix as defined in [RFC6839] except application/ld+json, update the active context using the Context Processing algorithm, passing the context referenced in the HTTP Link Header as local context. The HTTP Link Header is ignored for documents served as application/ld+json If multiple HTTP Link Headers using the http://www.w3.org/ns/json-ld#context link relation are found, the promise is rejected with a JsonLdError whose code is set to multiple context link headers and processing is terminated.
490
+ contextUrl = unless content_type.nil? || content_type.start_with?("application/ld+json")
491
+ # Get context link(s)
492
+ # Note, we can't simply use #find_link, as we need to detect multiple
493
+ links = remote_doc.links.links.select do |link|
494
+ link.attr_pairs.include?(%w(rel http://www.w3.org/ns/json-ld#context))
531
495
  end
496
+ raise JSON::LD::JsonLdError::MultipleContextLinkHeaders,
497
+ "expected at most 1 Link header with rel=jsonld:context, got #{links.length}" if links.length > 1
498
+ Array(links.first).first
499
+ end
532
500
 
533
- return block_given? ? yield(remote_document) : remote_document
501
+ doc_uri = remote_doc.base_uri rescue url
502
+ doc = RemoteDocument.new(doc_uri, remote_doc.read, contextUrl)
503
+ if block_given?
504
+ yield(doc)
505
+ else
506
+ doc
534
507
  end
535
508
  end
509
+ rescue IOError => e
510
+ raise JSON::LD::JsonLdError::LoadingDocumentFailed, e.message
536
511
  end
537
512
 
538
513
  # Add class method aliases for backwards compatibility