rdf 1.1.17.1 → 1.99.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.
- checksums.yaml +4 -4
- data/README +9 -9
- data/VERSION +1 -1
- data/lib/rdf/cli.rb +10 -10
- data/lib/rdf/format.rb +9 -9
- data/lib/rdf/mixin/enumerable.rb +66 -38
- data/lib/rdf/mixin/mutable.rb +11 -5
- data/lib/rdf/mixin/queryable.rb +2 -2
- data/lib/rdf/model/graph.rb +60 -40
- data/lib/rdf/model/list.rb +9 -9
- data/lib/rdf/model/literal/boolean.rb +1 -1
- data/lib/rdf/model/literal/date.rb +1 -1
- data/lib/rdf/model/literal/datetime.rb +5 -5
- data/lib/rdf/model/literal/decimal.rb +1 -1
- data/lib/rdf/model/literal/double.rb +3 -1
- data/lib/rdf/model/literal/integer.rb +1 -1
- data/lib/rdf/model/literal/time.rb +5 -5
- data/lib/rdf/model/literal/token.rb +1 -1
- data/lib/rdf/model/literal.rb +40 -6
- data/lib/rdf/model/node.rb +1 -1
- data/lib/rdf/model/statement.rb +88 -30
- data/lib/rdf/model/term.rb +10 -1
- data/lib/rdf/model/uri.rb +24 -25
- data/lib/rdf/nquads.rb +13 -22
- data/lib/rdf/ntriples/format.rb +4 -5
- data/lib/rdf/ntriples/reader.rb +10 -10
- data/lib/rdf/ntriples/writer.rb +6 -6
- data/lib/rdf/query/pattern.rb +49 -35
- data/lib/rdf/query/solution.rb +1 -1
- data/lib/rdf/query/solutions.rb +4 -4
- data/lib/rdf/query.rb +81 -40
- data/lib/rdf/reader.rb +22 -5
- data/lib/rdf/repository.rb +86 -37
- data/lib/rdf/transaction.rb +41 -20
- data/lib/rdf/util/file.rb +35 -18
- data/lib/rdf/vocab/schema.rb +5129 -5127
- data/lib/rdf/vocabulary.rb +43 -60
- data/lib/rdf/writer.rb +22 -12
- data/lib/rdf.rb +19 -4
- metadata +19 -5
data/lib/rdf/repository.rb
CHANGED
@@ -96,13 +96,19 @@ module RDF
|
|
96
96
|
# @param [Hash{Symbol => Object}] options
|
97
97
|
# @option options [URI, #to_s] :uri (nil)
|
98
98
|
# @option options [String, #to_s] :title (nil)
|
99
|
-
# @option options [Boolean] :
|
99
|
+
# @option options [Boolean] :with_graph_name (true)
|
100
100
|
# Indicates that the repository supports named graphs, otherwise,
|
101
101
|
# only the default graph is supported.
|
102
|
+
# @option options [Boolean] :with_context (true)
|
103
|
+
# Alias for :with_graph_name. :with_context is deprecated in RDF.rb 2.0.
|
102
104
|
# @yield [repository]
|
103
105
|
# @yieldparam [Repository] repository
|
104
106
|
def initialize(options = {}, &block)
|
105
|
-
|
107
|
+
if options[:with_context]
|
108
|
+
warn "[DEPRECATION] the :contexts option to Repository#initialize is deprecated in RDF.rb 2.0, use :graph_name instead. Called from #{Gem.location_of_caller.join(':')}"
|
109
|
+
options[:graph_name] ||= options.delete(:with_context)
|
110
|
+
end
|
111
|
+
@options = {with_graph_name: true}.merge(options)
|
106
112
|
@uri = @options.delete(:uri)
|
107
113
|
@title = @options.delete(:title)
|
108
114
|
|
@@ -140,20 +146,20 @@ module RDF
|
|
140
146
|
#
|
141
147
|
# @example
|
142
148
|
# repository.transaction do |tx|
|
143
|
-
# tx.insert [RDF::URI("http://rubygems.org/gems/rdf"), RDF::
|
149
|
+
# tx.insert [RDF::URI("http://rubygems.org/gems/rdf"), RDF::RDFS.label, "RDF.rb"]
|
144
150
|
# end
|
145
151
|
#
|
146
|
-
# @param [RDF::Resource]
|
152
|
+
# @param [RDF::Resource] graph_name
|
147
153
|
# Context on which to run the transaction, use `false` for the default
|
148
|
-
#
|
154
|
+
# graph_name and `nil` the entire Repository
|
149
155
|
# @yield [tx]
|
150
156
|
# @yieldparam [RDF::Transaction] tx
|
151
157
|
# @yieldreturn [void] ignored
|
152
|
-
# @return [
|
158
|
+
# @return [self]
|
153
159
|
# @see RDF::Transaction
|
154
160
|
# @since 0.3.0
|
155
|
-
def transaction(
|
156
|
-
tx = begin_transaction(
|
161
|
+
def transaction(graph_name = nil, &block)
|
162
|
+
tx = begin_transaction(graph_name)
|
157
163
|
begin
|
158
164
|
case block.arity
|
159
165
|
when 1 then block.call(tx)
|
@@ -177,11 +183,11 @@ module RDF
|
|
177
183
|
# to override this method in order to begin a transaction against the
|
178
184
|
# underlying storage.
|
179
185
|
#
|
180
|
-
# @param [RDF::Resource]
|
186
|
+
# @param [RDF::Resource] graph_name
|
181
187
|
# @return [RDF::Transaction]
|
182
188
|
# @since 0.3.0
|
183
|
-
def begin_transaction(
|
184
|
-
RDF::Transaction.new(:
|
189
|
+
def begin_transaction(graph_name)
|
190
|
+
RDF::Transaction.new(graph: graph_name)
|
185
191
|
end
|
186
192
|
|
187
193
|
##
|
@@ -215,7 +221,7 @@ module RDF
|
|
215
221
|
##
|
216
222
|
# @see RDF::Repository
|
217
223
|
module Implementation
|
218
|
-
|
224
|
+
DEFAULT_GRAPH = false
|
219
225
|
|
220
226
|
##
|
221
227
|
# @private
|
@@ -229,8 +235,11 @@ module RDF
|
|
229
235
|
# @see RDF::Enumerable#supports?
|
230
236
|
def supports?(feature)
|
231
237
|
case feature.to_sym
|
232
|
-
#
|
233
|
-
when :context
|
238
|
+
#statement named graphs
|
239
|
+
when :context
|
240
|
+
warn "[DEPRECATION] the :context feature is deprecated in RDF.rb 2.0; use :graph_name instead. Called from #{Gem.location_of_caller.join(':')}"
|
241
|
+
@options[:with_context] || @options[:with_graph_name]
|
242
|
+
when :graph_name then @options[:with_graph_name]
|
234
243
|
when :inference then false # forward-chaining inference
|
235
244
|
when :validity then @options.fetch(:with_validity, true)
|
236
245
|
else false
|
@@ -249,7 +258,7 @@ module RDF
|
|
249
258
|
# @see RDF::Countable#count
|
250
259
|
def count
|
251
260
|
count = 0
|
252
|
-
@data.each do |
|
261
|
+
@data.each do |g, ss|
|
253
262
|
ss.each do |s, ps|
|
254
263
|
ps.each do |p, os|
|
255
264
|
count += os.size
|
@@ -263,12 +272,12 @@ module RDF
|
|
263
272
|
# @private
|
264
273
|
# @see RDF::Enumerable#has_statement?
|
265
274
|
def has_statement?(statement)
|
266
|
-
s, p, o,
|
267
|
-
|
268
|
-
@data.has_key?(
|
269
|
-
@data[
|
270
|
-
@data[
|
271
|
-
@data[
|
275
|
+
s, p, o, g = statement.to_quad
|
276
|
+
g ||= DEFAULT_GRAPH
|
277
|
+
@data.has_key?(g) &&
|
278
|
+
@data[g].has_key?(s) &&
|
279
|
+
@data[g][s].has_key?(p) &&
|
280
|
+
@data[g][s][p].include?(o)
|
272
281
|
end
|
273
282
|
|
274
283
|
##
|
@@ -280,12 +289,12 @@ module RDF
|
|
280
289
|
# possible concurrent mutations to `@data`, we use `#dup` to make
|
281
290
|
# shallow copies of the nested hashes before beginning the
|
282
291
|
# iteration over their keys and values.
|
283
|
-
@data.dup.each do |
|
292
|
+
@data.dup.each do |g, ss|
|
284
293
|
ss.dup.each do |s, ps|
|
285
294
|
ps.dup.each do |p, os|
|
286
295
|
os.dup.each do |o|
|
287
296
|
# FIXME: yield has better performance, but broken in MRI 2.2: See https://bugs.ruby-lang.org/issues/11451.
|
288
|
-
block.call(RDF::Statement.new(s, p, o, :
|
297
|
+
block.call(RDF::Statement.new(s, p, o, graph_name: g.equal?(DEFAULT_GRAPH) ? nil : g))
|
289
298
|
end
|
290
299
|
end
|
291
300
|
end
|
@@ -298,48 +307,87 @@ module RDF
|
|
298
307
|
##
|
299
308
|
# @private
|
300
309
|
# @see RDF::Enumerable#has_context?
|
310
|
+
# @deprecated Use {#has_graph?} instead.
|
301
311
|
def has_context?(value)
|
312
|
+
warn "[DEPRECATION] Repository#has_context? is deprecated in RDF.rb 2.0, use Repository#has_graph? instead. Called from #{Gem.location_of_caller.join(':')}"
|
313
|
+
has_graph?(value)
|
314
|
+
end
|
315
|
+
|
316
|
+
##
|
317
|
+
# @private
|
318
|
+
# @see RDF::Enumerable#has_graph?
|
319
|
+
def has_graph?(value)
|
302
320
|
@data.keys.include?(value)
|
303
321
|
end
|
322
|
+
##
|
323
|
+
# @private
|
324
|
+
# @see RDF::Enumerable#each_graph
|
325
|
+
def graph_names(options = nil, &block)
|
326
|
+
@data.keys.reject {|g| g == DEFAULT_GRAPH}
|
327
|
+
end
|
304
328
|
|
305
329
|
##
|
306
330
|
# @private
|
307
331
|
# @see RDF::Enumerable#each_context
|
332
|
+
# @deprecated Use {#each_graph} instead.
|
308
333
|
def each_context(&block)
|
334
|
+
warn "[DEPRECATION] Repository#each_context is deprecated in RDF.rb 2.0, use Repository#each_graph instead. Called from #{Gem.location_of_caller.join(':')}"
|
309
335
|
if block_given?
|
310
336
|
contexts = @data.keys
|
311
|
-
contexts.delete(
|
337
|
+
contexts.delete(DEFAULT_GRAPH)
|
312
338
|
contexts.each(&block)
|
313
339
|
end
|
314
340
|
enum_context
|
315
341
|
end
|
316
342
|
|
343
|
+
##
|
344
|
+
# @private
|
345
|
+
# @see RDF::Enumerable#each_graph
|
346
|
+
def each_graph(&block)
|
347
|
+
if block_given?
|
348
|
+
@data.each_key do |gn|
|
349
|
+
yield RDF::Graph.new(gn == DEFAULT_GRAPH ? nil : gn, data: self)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
enum_graph
|
353
|
+
end
|
354
|
+
|
317
355
|
protected
|
318
356
|
|
319
357
|
##
|
320
358
|
# Match elements with eql?, not ==
|
321
|
-
# Context of `false` matches default
|
359
|
+
# Context of `false` matches default graph. Unbound variable matches non-false graph name
|
322
360
|
# @private
|
323
361
|
# @see RDF::Queryable#query
|
324
362
|
def query_pattern(pattern, &block)
|
325
|
-
|
363
|
+
graph_name = pattern.graph_name
|
326
364
|
subject = pattern.subject
|
327
365
|
predicate = pattern.predicate
|
328
366
|
object = pattern.object
|
329
367
|
|
330
|
-
cs = @data.has_key?(
|
368
|
+
cs = @data.has_key?(graph_name) ? {graph_name => @data[graph_name]} : @data.dup
|
331
369
|
cs.each do |c, ss|
|
332
|
-
next unless
|
333
|
-
ss = ss.has_key?(subject)
|
370
|
+
next unless graph_name.nil? || graph_name == false && !c || graph_name.eql?(c)
|
371
|
+
ss = if ss.has_key?(subject)
|
372
|
+
{ subject => ss[subject] }
|
373
|
+
elsif subject.nil? || subject.is_a?(RDF::Query::Variable)
|
374
|
+
ss.dup
|
375
|
+
else
|
376
|
+
[]
|
377
|
+
end
|
334
378
|
ss.each do |s, ps|
|
335
|
-
|
336
|
-
|
379
|
+
ps = if ps.has_key?(predicate)
|
380
|
+
{ predicate => ps[predicate] }
|
381
|
+
elsif predicate.nil? || predicate.is_a?(RDF::Query::Variable)
|
382
|
+
ps.dup
|
383
|
+
else
|
384
|
+
[]
|
385
|
+
end
|
337
386
|
ps.each do |p, os|
|
338
|
-
next unless predicate.nil? || predicate.eql?(p)
|
339
387
|
os = os.dup # TODO: is this really needed?
|
340
388
|
os.each do |o|
|
341
389
|
next unless object.nil? || object.eql?(o)
|
342
|
-
block.call(RDF::Statement.new(s, p, o, :
|
390
|
+
block.call(RDF::Statement.new(s, p, o, graph_name: c.equal?(DEFAULT_GRAPH) ? nil : c))
|
343
391
|
end
|
344
392
|
end
|
345
393
|
end
|
@@ -350,10 +398,11 @@ module RDF
|
|
350
398
|
# @private
|
351
399
|
# @see RDF::Mutable#insert
|
352
400
|
def insert_statement(statement)
|
401
|
+
raise ArgumentError, "Statement #{statement.inspect} is incomplete" if statement.incomplete?
|
353
402
|
unless has_statement?(statement)
|
354
403
|
s, p, o, c = statement.to_quad
|
355
|
-
c =
|
356
|
-
c ||=
|
404
|
+
c = DEFAULT_GRAPH unless supports?(:graph_name)
|
405
|
+
c ||= DEFAULT_GRAPH
|
357
406
|
@data[c] ||= {}
|
358
407
|
@data[c][s] ||= {}
|
359
408
|
@data[c][s][p] ||= []
|
@@ -367,8 +416,8 @@ module RDF
|
|
367
416
|
def delete_statement(statement)
|
368
417
|
if has_statement?(statement)
|
369
418
|
s, p, o, c = statement.to_quad
|
370
|
-
c =
|
371
|
-
c ||=
|
419
|
+
c = DEFAULT_GRAPH unless supports?(:graph_name)
|
420
|
+
c ||= DEFAULT_GRAPH
|
372
421
|
@data[c][s][p].delete(o)
|
373
422
|
@data[c][s].delete(p) if @data[c][s][p].empty?
|
374
423
|
@data[c].delete(s) if @data[c][s].empty?
|
data/lib/rdf/transaction.rb
CHANGED
@@ -13,8 +13,8 @@ module RDF
|
|
13
13
|
# repository = ...
|
14
14
|
# RDF::Transaction.execute(repository) do |tx|
|
15
15
|
# subject = RDF::URI("http://example.org/article")
|
16
|
-
# tx.delete [subject, RDF::
|
17
|
-
# tx.insert [subject, RDF::
|
16
|
+
# tx.delete [subject, RDF::RDFS.label, "Old title"]
|
17
|
+
# tx.insert [subject, RDF::RDFS.label, "New title"]
|
18
18
|
# end
|
19
19
|
#
|
20
20
|
# @since 0.3.0
|
@@ -35,13 +35,25 @@ module RDF
|
|
35
35
|
|
36
36
|
##
|
37
37
|
# Name of this graph, if it is part of an {RDF::Repository}
|
38
|
-
# @!attribute [rw]
|
38
|
+
# @!attribute [rw] graph_name
|
39
39
|
# @return [RDF::Resource]
|
40
40
|
# @since 1.1.0
|
41
|
-
attr_accessor :
|
41
|
+
attr_accessor :graph_name
|
42
42
|
|
43
|
-
|
44
|
-
|
43
|
+
# @deprecated Use {#graph_name} instead.
|
44
|
+
def context
|
45
|
+
warn "[DEPRECATION] Statement#context is being replaced with Statement@graph_name in RDF.rb 2.0. Called from #{Gem.location_of_caller.join(':')}"
|
46
|
+
graph_name
|
47
|
+
end
|
48
|
+
|
49
|
+
# @deprecated Use {#graph_name=} instead.
|
50
|
+
def context=(value)
|
51
|
+
warn "[DEPRECATION] Statement#context= is being replaced with Statement@graph_name= in RDF.rb 2.0. Called from #{Gem.location_of_caller.join(':')}"
|
52
|
+
self.graph_name = value
|
53
|
+
end
|
54
|
+
|
55
|
+
alias_method :graph, :graph_name
|
56
|
+
alias_method :graph=, :graph_name=
|
45
57
|
|
46
58
|
##
|
47
59
|
# RDF statements to delete when executed.
|
@@ -66,19 +78,25 @@ module RDF
|
|
66
78
|
#
|
67
79
|
# @param [Hash{Symbol => Object}] options
|
68
80
|
# @option options [RDF::Resource] :context (nil)
|
81
|
+
# Alias for `:graph_name`. Deprected in RDF.rb 2.0.
|
82
|
+
# @option options [RDF::Resource] :graph_name (nil)
|
69
83
|
# Name of named graph to be affected if `inserts` or `deletes`
|
70
|
-
# do not have a `
|
84
|
+
# do not have a `graph_name`.
|
71
85
|
# @option options [RDF::Resource] :graph (nil)
|
72
|
-
# Alias for `:
|
86
|
+
# Alias for `:graph_name`.
|
73
87
|
# @option options [RDF::Enumerable] :insert (RDF::Graph.new)
|
74
88
|
# @option options [RDF::Enumerable] :delete (RDF::Graph.new)
|
75
89
|
# @yield [tx]
|
76
90
|
# @yieldparam [RDF::Transaction] tx
|
77
91
|
def initialize(options = {}, &block)
|
78
92
|
@options = options.dup
|
79
|
-
|
80
|
-
|
81
|
-
|
93
|
+
if @options.has_key?(:context)
|
94
|
+
warn "[DEPRECATION] the :contexts option to Mutable#load is deprecated in RDF.rb 2.0, use :graph_name instead. Called from #{Gem.location_of_caller.join(':')}"
|
95
|
+
@options[:graph_name] ||= @options.delete(:context)
|
96
|
+
end
|
97
|
+
@graph_name = @options.delete(:graph) || @options.delete(:graph_name)
|
98
|
+
@inserts = @options.delete(:insert) || []
|
99
|
+
@deletes = @options.delete(:delete) || []
|
82
100
|
@inserts.extend(RDF::Enumerable) unless @inserts.kind_of?(RDF::Enumerable)
|
83
101
|
@deletes.extend(RDF::Enumerable) unless @deletes.kind_of?(RDF::Enumerable)
|
84
102
|
|
@@ -112,18 +130,21 @@ module RDF
|
|
112
130
|
def execute(repository, options = {})
|
113
131
|
before_execute(repository, options) if respond_to?(:before_execute)
|
114
132
|
|
115
|
-
deletes.
|
116
|
-
statement =
|
117
|
-
statement.
|
118
|
-
|
133
|
+
dels = deletes.map do |s|
|
134
|
+
statement = s.dup
|
135
|
+
statement.graph_name ||= graph_name
|
136
|
+
statement
|
119
137
|
end
|
120
138
|
|
121
|
-
inserts.
|
122
|
-
statement =
|
123
|
-
statement.
|
124
|
-
|
139
|
+
ins = inserts.map do |s|
|
140
|
+
statement = s.dup
|
141
|
+
statement.graph_name ||= graph_name
|
142
|
+
statement
|
125
143
|
end
|
126
|
-
|
144
|
+
|
145
|
+
repository.delete(*dels) unless dels.empty?
|
146
|
+
repository.insert(*ins) unless ins.empty?
|
147
|
+
|
127
148
|
after_execute(repository, options) if respond_to?(:after_execute)
|
128
149
|
self
|
129
150
|
end
|
data/lib/rdf/util/file.rb
CHANGED
@@ -100,7 +100,6 @@ module RDF; module Util
|
|
100
100
|
# If a Location is returned, it defines the base resource for this file, not it's actual ending location
|
101
101
|
document_options = {
|
102
102
|
base_uri: RDF::URI(response.headers.fetch(:location, base_uri)),
|
103
|
-
charset: Encoding::UTF_8,
|
104
103
|
code: response.code.to_i,
|
105
104
|
headers: response.headers
|
106
105
|
}
|
@@ -159,7 +158,6 @@ module RDF; module Util
|
|
159
158
|
# If a Location is returned, it defines the base resource for this file, not it's actual ending location
|
160
159
|
document_options = {
|
161
160
|
base_uri: RDF::URI(response["Location"] ? response["Location"] : base_uri),
|
162
|
-
charset: Encoding::UTF_8,
|
163
161
|
code: response.code.to_i,
|
164
162
|
content_type: response.content_type,
|
165
163
|
headers: response_headers
|
@@ -226,7 +224,6 @@ module RDF; module Util
|
|
226
224
|
# If a Location is returned, it defines the base resource for this file, not it's actual ending location
|
227
225
|
document_options = {
|
228
226
|
base_uri: RDF::URI(response.headers.fetch(:location, response.env.url)),
|
229
|
-
charset: Encoding::UTF_8,
|
230
227
|
code: response.status,
|
231
228
|
headers: response.headers
|
232
229
|
}
|
@@ -279,6 +276,8 @@ module RDF; module Util
|
|
279
276
|
# Adds Accept header based on available reader content types to allow
|
280
277
|
# for content negotiation based on available readers.
|
281
278
|
#
|
279
|
+
# Input received as non-unicode, is transformed to UTF-8. With Ruby >= 2.2, all UTF is normalized to [Unicode Normalization Form C (NFC)](http://unicode.org/reports/tr15/#Norm_Forms).
|
280
|
+
#
|
282
281
|
# HTTP resources may be retrieved via proxy using the `proxy` option. If `RestClient` is loaded, they will use the proxy globally by setting something like the following:
|
283
282
|
# `RestClient.proxy = "http://proxy.example.com/"`.
|
284
283
|
# When retrieving documents over HTTP(S), use the mechanism described in [Providing and Discovering URI Documentation](http://www.w3.org/2001/tag/awwsw/issue57/latest/) to pass the appropriate `base_uri` to the block or as the return.
|
@@ -328,11 +327,11 @@ module RDF; module Util
|
|
328
327
|
Kernel.open(filename_or_url, "r:utf-8", options) do |file|
|
329
328
|
document_options = {
|
330
329
|
base_uri: filename_or_url.to_s,
|
331
|
-
charset: file.external_encoding,
|
330
|
+
charset: file.external_encoding.to_s,
|
332
331
|
code: 200,
|
333
332
|
content_type: content_type,
|
334
333
|
last_modified:file.mtime,
|
335
|
-
headers: {
|
334
|
+
headers: {content_type: content_type, last_modified: file.mtime.xmlschema}
|
336
335
|
}
|
337
336
|
|
338
337
|
remote_document = RemoteDocument.new(file.read, document_options)
|
@@ -363,8 +362,8 @@ module RDF; module Util
|
|
363
362
|
# @return [String]
|
364
363
|
attr_reader :content_type
|
365
364
|
|
366
|
-
# Encoding of resource (from
|
367
|
-
# @return [
|
365
|
+
# Encoding of resource (from Content-Type), downcased. Also applied to content if it is UTF
|
366
|
+
# @return [String}]
|
368
367
|
attr_reader :charset
|
369
368
|
|
370
369
|
# Response code
|
@@ -381,7 +380,7 @@ module RDF; module Util
|
|
381
380
|
attr_reader :last_modified
|
382
381
|
|
383
382
|
# Raw headers from response
|
384
|
-
# @return [Hash{
|
383
|
+
# @return [Hash{Symbol => Object}]
|
385
384
|
attr_reader :headers
|
386
385
|
|
387
386
|
# Originally requested URL
|
@@ -390,40 +389,58 @@ module RDF; module Util
|
|
390
389
|
|
391
390
|
##
|
392
391
|
# Set content
|
393
|
-
# @param [String] body
|
392
|
+
# @param [String] body entity content of request.
|
394
393
|
def initialize(body, options = {})
|
395
|
-
super(body)
|
396
394
|
options.each do |key, value|
|
397
395
|
# de-quote charset
|
398
396
|
matchdata = value.match(/^["'](.*)["']$/.freeze) if key == "charset"
|
399
397
|
value = matchdata[1] if matchdata
|
398
|
+
value = value.downcase if value.is_a?(String)
|
400
399
|
instance_variable_set(:"@#{key}", value)
|
401
400
|
end
|
402
|
-
@headers
|
401
|
+
@headers = options.fetch(:headers, {})
|
402
|
+
@charset = options[:charset].to_s.downcase if options[:charset]
|
403
403
|
|
404
404
|
# Find Content-Type
|
405
|
-
if
|
406
|
-
|
405
|
+
if headers[:content_type]
|
406
|
+
ct, *params = headers[:content_type].split(';').map(&:strip)
|
407
|
+
@content_type ||= ct
|
407
408
|
|
408
409
|
# Find charset
|
409
410
|
params.each do |param|
|
410
411
|
p, v = param.split('=')
|
411
412
|
next unless p.downcase == 'charset'
|
412
|
-
@charset
|
413
|
+
@charset ||= v.sub(/^["']?(.*)["']?$/, '\1').downcase
|
413
414
|
end
|
414
415
|
end
|
415
416
|
|
416
417
|
@etag = headers[:etag]
|
417
418
|
@last_modified = DateTime.parse(headers[:last_modified]) if headers[:last_modified]
|
419
|
+
encoding = @charset ||= "utf-8"
|
420
|
+
|
421
|
+
unless encoding.start_with?("utf")
|
422
|
+
body.force_encoding(Encoding::UTF_8)
|
423
|
+
encoding = "utf-8"
|
424
|
+
end
|
418
425
|
|
419
|
-
|
426
|
+
# Make sure Unicode is in NFC
|
427
|
+
|
428
|
+
begin
|
429
|
+
body.unicode_normalize! unless !body.unicode_normalized?
|
430
|
+
rescue Encoding::CompatibilityError
|
431
|
+
# Oh, well ...
|
432
|
+
end if body.respond_to?(:unicode_normalized?)
|
433
|
+
|
434
|
+
super(body, "r:#{encoding}")
|
420
435
|
end
|
421
436
|
|
422
437
|
##
|
423
|
-
# Content
|
424
|
-
#
|
438
|
+
# Returns a list of encodings in Content-Encoding field as an array of strings.
|
439
|
+
#
|
440
|
+
# The encodings are downcased for canonicalization.
|
441
|
+
# @return [Array<String>]
|
425
442
|
def content_encoding
|
426
|
-
|
443
|
+
headers.fetch(:content_encoding, "").split(',').map(&:strip).map(&:downcase)
|
427
444
|
end
|
428
445
|
|
429
446
|
##
|