rdf 1.99.1 → 2.0.0.beta1
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 → README.md} +9 -44
- data/VERSION +1 -1
- data/bin/rdf +1 -1
- data/lib/rdf.rb +40 -49
- data/lib/rdf/changeset.rb +161 -0
- data/lib/rdf/cli.rb +195 -33
- data/lib/rdf/cli/vocab-loader.rb +13 -3
- data/lib/rdf/format.rb +44 -26
- data/lib/rdf/mixin/enumerable.rb +133 -97
- data/lib/rdf/mixin/enumerator.rb +8 -0
- data/lib/rdf/mixin/indexable.rb +1 -1
- data/lib/rdf/mixin/mutable.rb +101 -22
- data/lib/rdf/mixin/queryable.rb +21 -32
- data/lib/rdf/mixin/transactable.rb +94 -0
- data/lib/rdf/mixin/writable.rb +12 -3
- data/lib/rdf/model/dataset.rb +48 -0
- data/lib/rdf/model/graph.rb +73 -43
- data/lib/rdf/model/list.rb +61 -33
- data/lib/rdf/model/literal.rb +20 -19
- data/lib/rdf/model/literal/double.rb +20 -4
- data/lib/rdf/model/literal/numeric.rb +15 -13
- data/lib/rdf/model/node.rb +15 -16
- data/lib/rdf/model/statement.rb +1 -43
- data/lib/rdf/model/term.rb +10 -8
- data/lib/rdf/model/uri.rb +35 -34
- data/lib/rdf/model/value.rb +1 -1
- data/lib/rdf/nquads.rb +2 -11
- data/lib/rdf/ntriples.rb +1 -1
- data/lib/rdf/ntriples/reader.rb +33 -46
- data/lib/rdf/ntriples/writer.rb +42 -5
- data/lib/rdf/query.rb +6 -40
- data/lib/rdf/query/pattern.rb +4 -17
- data/lib/rdf/query/solutions.rb +6 -6
- data/lib/rdf/reader.rb +65 -14
- data/lib/rdf/repository.rb +365 -229
- data/lib/rdf/transaction.rb +211 -84
- data/lib/rdf/util.rb +1 -0
- data/lib/rdf/util/cache.rb +5 -5
- data/lib/rdf/util/file.rb +12 -9
- data/lib/rdf/util/logger.rb +272 -0
- data/lib/rdf/version.rb +2 -2
- data/lib/rdf/vocab/owl.rb +82 -77
- data/lib/rdf/vocab/rdfs.rb +22 -17
- data/lib/rdf/vocab/xsd.rb +5 -0
- data/lib/rdf/vocabulary.rb +50 -56
- data/lib/rdf/writer.rb +104 -52
- metadata +45 -90
- data/lib/rdf/mixin/inferable.rb +0 -5
- data/lib/rdf/vocab/cc.rb +0 -128
- data/lib/rdf/vocab/cert.rb +0 -245
- data/lib/rdf/vocab/dc.rb +0 -948
- data/lib/rdf/vocab/dc11.rb +0 -167
- data/lib/rdf/vocab/dcat.rb +0 -214
- data/lib/rdf/vocab/doap.rb +0 -337
- data/lib/rdf/vocab/exif.rb +0 -941
- data/lib/rdf/vocab/foaf.rb +0 -614
- data/lib/rdf/vocab/geo.rb +0 -157
- data/lib/rdf/vocab/gr.rb +0 -1501
- data/lib/rdf/vocab/ht.rb +0 -236
- data/lib/rdf/vocab/ical.rb +0 -528
- data/lib/rdf/vocab/ma.rb +0 -513
- data/lib/rdf/vocab/mo.rb +0 -2412
- data/lib/rdf/vocab/og.rb +0 -222
- data/lib/rdf/vocab/ogc.rb +0 -58
- data/lib/rdf/vocab/prov.rb +0 -1550
- data/lib/rdf/vocab/rsa.rb +0 -72
- data/lib/rdf/vocab/rss.rb +0 -66
- data/lib/rdf/vocab/schema.rb +0 -10569
- data/lib/rdf/vocab/sioc.rb +0 -669
- data/lib/rdf/vocab/skos.rb +0 -238
- data/lib/rdf/vocab/skosxl.rb +0 -57
- data/lib/rdf/vocab/v.rb +0 -383
- data/lib/rdf/vocab/vcard.rb +0 -841
- data/lib/rdf/vocab/vmd.rb +0 -383
- data/lib/rdf/vocab/void.rb +0 -186
- data/lib/rdf/vocab/vs.rb +0 -28
- data/lib/rdf/vocab/wdrs.rb +0 -134
- data/lib/rdf/vocab/wot.rb +0 -167
- data/lib/rdf/vocab/xhtml.rb +0 -8
- data/lib/rdf/vocab/xhv.rb +0 -505
data/lib/rdf/transaction.rb
CHANGED
@@ -2,70 +2,136 @@ module RDF
|
|
2
2
|
##
|
3
3
|
# An RDF transaction.
|
4
4
|
#
|
5
|
-
# Transactions
|
6
|
-
# a sequence of RDF statements to insert into a given named graph.
|
5
|
+
# Transactions provide an ACID scope for queries and mutations.
|
7
6
|
#
|
8
|
-
# Repository implementations may
|
9
|
-
#
|
10
|
-
#
|
7
|
+
# Repository implementations may provide support for transactional updates
|
8
|
+
# by providing an atomic implementation of {Mutable#apply_changeset} and
|
9
|
+
# responding to `#supports?(:atomic_write)` with `true`.
|
10
|
+
#
|
11
|
+
# We carefully distinguish between read-only and read/write transactions,
|
12
|
+
# in order to enable repository implementations to take out the
|
13
|
+
# appropriate locks for concurrency control. Transactions are read-only
|
14
|
+
# by default; mutability must be explicitly requested on construction in
|
15
|
+
# order to obtain a read/write transaction.
|
11
16
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
17
|
+
# Individual repositories may make their own sets of guarantees within the
|
18
|
+
# transaction's scope. In case repository implementations should be unable
|
19
|
+
# to provide full ACID guarantees for transactions, that must be clearly
|
20
|
+
# indicated in their documentation. If update atomicity is not provided,
|
21
|
+
# `#supports?(:atomic_write)` must respond `false`.
|
22
|
+
#
|
23
|
+
# @example Executing a read-only transaction
|
24
|
+
# repository = RDF::Repository.new
|
25
|
+
#
|
26
|
+
# RDF::Transaction.begin(repository) do |tx|
|
27
|
+
# tx.query(predicate: RDF::Vocab::DOAP.developer) do |statement|
|
28
|
+
# puts statement.inspect
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# @example Executing a read/write transaction
|
33
|
+
# repository = RDF::Repository.new
|
34
|
+
#
|
35
|
+
# RDF::Transaction.begin(repository, mutable: true) do |tx|
|
15
36
|
# subject = RDF::URI("http://example.org/article")
|
16
37
|
# tx.delete [subject, RDF::RDFS.label, "Old title"]
|
17
38
|
# tx.insert [subject, RDF::RDFS.label, "New title"]
|
18
39
|
# end
|
40
|
+
#
|
41
|
+
# The base class provides an atomic write implementation depending on
|
42
|
+
# `RDF::Changeset` and using `Changeset#apply`. Custom `Repositories`
|
43
|
+
# can implement a minimial write-atomic transactions by overriding
|
44
|
+
# `#apply_changeset`.
|
19
45
|
#
|
46
|
+
# Reads within a transaction run against the live repository by default
|
47
|
+
# (`#isolation_level' is `:read_committed`). Repositories may provide support
|
48
|
+
# for snapshots by implementing `Repository#snapshot` and responding `true` to
|
49
|
+
# `#supports?(:snapshots)`. In this case, the transaction will use the
|
50
|
+
# `RDF::Dataset` returned by `#snapshot` for reads (`:repeatable_read`).
|
51
|
+
#
|
52
|
+
# For datastores that support transactions natively, implementation of a
|
53
|
+
# custom `Transaction` subclass is recommended. The `Repository` is
|
54
|
+
# responsible for specifying snapshot support and isolation level as
|
55
|
+
# appropriate. Note that repositories may provide the snapshot isolation level
|
56
|
+
# without implementing `#snapshot`.
|
57
|
+
#
|
58
|
+
# @example A repository with a custom transaction class
|
59
|
+
# class MyRepository < RDF::Repository
|
60
|
+
# DEFAULT_TX_CLASS = MyTransaction
|
61
|
+
# # ...
|
62
|
+
# # custom repository logic
|
63
|
+
# # ...
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# @see RDF::Changeset
|
67
|
+
# @see RDF::Mutable#apply_changeset
|
20
68
|
# @since 0.3.0
|
21
69
|
class Transaction
|
22
70
|
include RDF::Mutable
|
71
|
+
include RDF::Enumerable
|
72
|
+
include RDF::Queryable
|
73
|
+
|
74
|
+
##
|
75
|
+
# @see RDF::Enumerable#each
|
76
|
+
def each(*args, &block)
|
77
|
+
read_target.each(*args, &block)
|
78
|
+
end
|
23
79
|
|
24
80
|
##
|
25
81
|
# Executes a transaction against the given RDF repository.
|
26
82
|
#
|
27
83
|
# @param [RDF::Repository] repository
|
84
|
+
# @param [Boolean] mutable (false)
|
85
|
+
# Whether this is a read-only or read/write transaction.
|
28
86
|
# @param [Hash{Symbol => Object}] options
|
29
87
|
# @yield [tx]
|
30
88
|
# @yieldparam [RDF::Transaction] tx
|
31
89
|
# @return [void]
|
32
|
-
def self.
|
33
|
-
self.new(
|
90
|
+
def self.begin(repository, mutable: false, **options, &block)
|
91
|
+
self.new(repository, options.merge(mutable: mutable), &block)
|
34
92
|
end
|
35
93
|
|
36
94
|
##
|
37
|
-
#
|
38
|
-
#
|
39
|
-
# @return [RDF::
|
40
|
-
# @since
|
41
|
-
|
42
|
-
|
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
|
95
|
+
# The repository being operated upon.
|
96
|
+
#
|
97
|
+
# @return [RDF::Repository]
|
98
|
+
# @since 2.0.0
|
99
|
+
attr_reader :repository
|
48
100
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
101
|
+
##
|
102
|
+
# The default graph name to apply to statements inserted or deleted by the
|
103
|
+
# transaction.
|
104
|
+
#
|
105
|
+
# @return [RDF::Resource, nil]
|
106
|
+
# @since 2.0.0
|
107
|
+
attr_reader :graph_name
|
54
108
|
|
55
|
-
|
56
|
-
|
109
|
+
##
|
110
|
+
# RDF statement mutations to apply when executed.
|
111
|
+
#
|
112
|
+
# @return [RDF::Changeset]
|
113
|
+
# @since 2.0.0
|
114
|
+
attr_reader :changes
|
57
115
|
|
58
116
|
##
|
59
117
|
# RDF statements to delete when executed.
|
60
118
|
#
|
119
|
+
# @deprecated
|
61
120
|
# @return [RDF::Enumerable]
|
62
121
|
attr_reader :deletes
|
122
|
+
def deletes
|
123
|
+
self.changes.deletes
|
124
|
+
end
|
63
125
|
|
64
126
|
##
|
65
127
|
# RDF statements to insert when executed.
|
66
128
|
#
|
129
|
+
# @deprecated
|
67
130
|
# @return [RDF::Enumerable]
|
68
131
|
attr_reader :inserts
|
132
|
+
def inserts
|
133
|
+
self.changes.inserts
|
134
|
+
end
|
69
135
|
|
70
136
|
##
|
71
137
|
# Any additional options for this transaction.
|
@@ -77,76 +143,70 @@ module RDF
|
|
77
143
|
# Initializes this transaction.
|
78
144
|
#
|
79
145
|
# @param [Hash{Symbol => Object}] options
|
80
|
-
# @
|
81
|
-
#
|
82
|
-
# @option options [RDF::Resource] :graph_name (nil)
|
83
|
-
# Name of named graph to be affected if `inserts` or `deletes`
|
84
|
-
# do not have a `graph_name`.
|
85
|
-
# @option options [RDF::Resource] :graph (nil)
|
86
|
-
# Alias for `:graph_name`.
|
87
|
-
# @option options [RDF::Enumerable] :insert (RDF::Graph.new)
|
88
|
-
# @option options [RDF::Enumerable] :delete (RDF::Graph.new)
|
146
|
+
# @param [Boolean] mutable (false)
|
147
|
+
# Whether this is a read-only or read/write transaction.
|
89
148
|
# @yield [tx]
|
90
149
|
# @yieldparam [RDF::Transaction] tx
|
91
|
-
def initialize(
|
92
|
-
@
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
@graph_name =
|
98
|
-
@inserts = @options.delete(:insert) || []
|
99
|
-
@deletes = @options.delete(:delete) || []
|
100
|
-
@inserts.extend(RDF::Enumerable) unless @inserts.kind_of?(RDF::Enumerable)
|
101
|
-
@deletes.extend(RDF::Enumerable) unless @deletes.kind_of?(RDF::Enumerable)
|
150
|
+
def initialize(repository, graph_name: nil, mutable: false, **options, &block)
|
151
|
+
@repository = repository
|
152
|
+
@snapshot =
|
153
|
+
repository.supports?(:snapshots) ? repository.snapshot : repository
|
154
|
+
@options = options.dup
|
155
|
+
@mutable = mutable
|
156
|
+
@graph_name = graph_name
|
102
157
|
|
158
|
+
raise TransactionError,
|
159
|
+
'Tried to open a mutable transaction on an immutable repository' if
|
160
|
+
@mutable && !@repository.mutable?
|
161
|
+
|
162
|
+
@changes = RDF::Changeset.new
|
163
|
+
|
103
164
|
if block_given?
|
104
165
|
case block.arity
|
105
166
|
when 1 then block.call(self)
|
106
|
-
else instance_eval(&block)
|
167
|
+
else self.instance_eval(&block)
|
107
168
|
end
|
108
169
|
end
|
109
170
|
end
|
171
|
+
|
172
|
+
##
|
173
|
+
# @see RDF::Dataset#isolation_level
|
174
|
+
def isolation_level
|
175
|
+
return :repeatable_read if repository.supports?(:snapshots)
|
176
|
+
:read_committed
|
177
|
+
end
|
110
178
|
|
111
179
|
##
|
112
|
-
# Returns `
|
113
|
-
#
|
114
|
-
# Transactions do not support the `RDF::Enumerable` protocol directly.
|
115
|
-
# To enumerate the RDF statements to be inserted or deleted, use the
|
116
|
-
# {RDF::Transaction#inserts} and {RDF::Transaction#deletes} accessors.
|
180
|
+
# Returns `true` if this is a read/write transaction, `false` otherwise.
|
117
181
|
#
|
118
182
|
# @return [Boolean]
|
119
|
-
# @see
|
120
|
-
def
|
121
|
-
|
183
|
+
# @see RDF::Writable#writable?
|
184
|
+
def writable?
|
185
|
+
@mutable
|
122
186
|
end
|
123
187
|
|
124
188
|
##
|
125
|
-
#
|
189
|
+
# Returns `true` if this is a read/write transaction, `false` otherwise.
|
126
190
|
#
|
127
|
-
# @
|
128
|
-
# @
|
129
|
-
|
130
|
-
|
131
|
-
|
191
|
+
# @return [Boolean]
|
192
|
+
# @see RDF::Writable#mutable?
|
193
|
+
def mutable?
|
194
|
+
@mutable
|
195
|
+
end
|
132
196
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
197
|
+
##
|
198
|
+
# Returns `true` to indicate that this transaction is readable.
|
199
|
+
#
|
200
|
+
# @return [Boolean]
|
201
|
+
# @see RDF::Readable#readable?
|
202
|
+
def readable?
|
203
|
+
true
|
204
|
+
end
|
138
205
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
end
|
144
|
-
|
145
|
-
repository.delete(*dels) unless dels.empty?
|
146
|
-
repository.insert(*ins) unless ins.empty?
|
147
|
-
|
148
|
-
after_execute(repository, options) if respond_to?(:after_execute)
|
149
|
-
self
|
206
|
+
##
|
207
|
+
# @see RDF::Enumerable#has_statement?
|
208
|
+
def has_statement?(statement)
|
209
|
+
read_target.has_statement?(statement)
|
150
210
|
end
|
151
211
|
|
152
212
|
##
|
@@ -154,8 +214,8 @@ module RDF
|
|
154
214
|
#
|
155
215
|
# @return [String]
|
156
216
|
def inspect
|
157
|
-
sprintf("#<%s:%#0x(
|
158
|
-
|
217
|
+
sprintf("#<%s:%#0x(changes: -%d/+%d)>", self.class.name,
|
218
|
+
self.__id__, self.changes.deletes.count, self.changes.inserts.count)
|
159
219
|
end
|
160
220
|
|
161
221
|
##
|
@@ -164,10 +224,38 @@ module RDF
|
|
164
224
|
#
|
165
225
|
# @return [void]
|
166
226
|
def inspect!
|
167
|
-
|
227
|
+
$stderr.puts(inspect)
|
228
|
+
end
|
229
|
+
|
230
|
+
##
|
231
|
+
# Executes the transaction
|
232
|
+
#
|
233
|
+
# @return [Boolean] `true` if the changes are successfully applied.
|
234
|
+
# @raise [TransactionError] if the transaction can't be applied
|
235
|
+
def execute
|
236
|
+
raise TransactionError, 'Cannot execute a rolled back transaction. ' \
|
237
|
+
'Open a new one instead.' if @rolledback
|
238
|
+
@changes.apply(@repository)
|
239
|
+
end
|
240
|
+
|
241
|
+
##
|
242
|
+
# Rolls back the transaction
|
243
|
+
#
|
244
|
+
# @note: the base class simply replaces its current `Changeset` with a
|
245
|
+
# fresh one. Other implementations may need to explictly rollback
|
246
|
+
# at the supporting datastore.
|
247
|
+
#
|
248
|
+
# @note: clients should not rely on using same transaction instance after
|
249
|
+
# rollback.
|
250
|
+
#
|
251
|
+
# @return [Boolean] `true` if the changes are successfully applied.
|
252
|
+
def rollback
|
253
|
+
@changes = RDF::Changeset.new
|
254
|
+
@rolledback = true
|
168
255
|
end
|
169
256
|
|
170
257
|
protected
|
258
|
+
|
171
259
|
##
|
172
260
|
# Appends an RDF statement to the sequence to insert when executed.
|
173
261
|
#
|
@@ -175,7 +263,7 @@ module RDF
|
|
175
263
|
# @return [void]
|
176
264
|
# @see RDF::Writable#insert_statement
|
177
265
|
def insert_statement(statement)
|
178
|
-
|
266
|
+
@changes.insert(process_statement(statement))
|
179
267
|
end
|
180
268
|
|
181
269
|
##
|
@@ -185,9 +273,48 @@ module RDF
|
|
185
273
|
# @return [void]
|
186
274
|
# @see RDF::Mutable#delete_statement
|
187
275
|
def delete_statement(statement)
|
188
|
-
|
276
|
+
@changes.delete(process_statement(statement))
|
277
|
+
end
|
278
|
+
|
279
|
+
def query_pattern(*args, &block)
|
280
|
+
read_target.send(:query_pattern, *args, &block)
|
189
281
|
end
|
190
282
|
|
283
|
+
def query_execute(*args, &block)
|
284
|
+
read_target.send(:query_execute, *args, &block)
|
285
|
+
end
|
286
|
+
|
191
287
|
undef_method :load, :update, :clear
|
288
|
+
|
289
|
+
private
|
290
|
+
|
291
|
+
##
|
292
|
+
# @private Adds the default graph_name to the statement, when one it does
|
293
|
+
# not already have one.
|
294
|
+
#
|
295
|
+
# @param statement [RDF::Statement]
|
296
|
+
# @return [RDF::Statement]
|
297
|
+
def process_statement(statement)
|
298
|
+
if graph_name
|
299
|
+
statement = statement.dup
|
300
|
+
statement.graph_name = graph_name
|
301
|
+
end
|
302
|
+
statement
|
303
|
+
end
|
304
|
+
|
305
|
+
def read_target
|
306
|
+
return @snapshot if graph_name.nil?
|
307
|
+
return @snapshot.project_graph(nil) if graph_name == false
|
308
|
+
@snapshot.project_graph(graph_name)
|
309
|
+
end
|
310
|
+
|
311
|
+
public
|
312
|
+
|
313
|
+
##
|
314
|
+
# An error class for transaction failures.
|
315
|
+
#
|
316
|
+
# This error indicates that the transaction semantics have been violated in
|
317
|
+
# some way.
|
318
|
+
class TransactionError < RuntimeError; end
|
192
319
|
end # Transaction
|
193
320
|
end # RDF
|
data/lib/rdf/util.rb
CHANGED
data/lib/rdf/util/cache.rb
CHANGED
@@ -56,12 +56,12 @@ module RDF; module Util
|
|
56
56
|
|
57
57
|
##
|
58
58
|
# This implementation relies on `ObjectSpace#_id2ref` and performs
|
59
|
-
# optimally on Ruby >=
|
59
|
+
# optimally on Ruby >= 2.x; however, it does not work on JRuby
|
60
60
|
# by default since much `ObjectSpace` functionality on that platform is
|
61
61
|
# disabled unless the `-X+O` startup option is given.
|
62
62
|
#
|
63
|
-
# @see http://
|
64
|
-
# @see http://
|
63
|
+
# @see http://ruby-doc.org/core-2.2.2/ObjectSpace.html
|
64
|
+
# @see http://ruby-doc.org/stdlib-2.2.0/libdoc/weakref/rdoc/WeakRef.html
|
65
65
|
class ObjectSpaceCache < Cache
|
66
66
|
##
|
67
67
|
# @param [Object] key
|
@@ -100,9 +100,9 @@ module RDF; module Util
|
|
100
100
|
|
101
101
|
##
|
102
102
|
# This implementation uses the `WeakRef` class from Ruby's standard
|
103
|
-
# library, and provides adequate performance on JRuby and on Ruby
|
103
|
+
# library, and provides adequate performance on JRuby and on Ruby 2.x.
|
104
104
|
#
|
105
|
-
# @see http://ruby-doc.org/
|
105
|
+
# @see http://ruby-doc.org/stdlib-2.2.0/libdoc/weakref/rdoc/WeakRef.html
|
106
106
|
class WeakRefCache < Cache
|
107
107
|
##
|
108
108
|
# @param [Integer] capacity
|
data/lib/rdf/util/file.rb
CHANGED
@@ -324,7 +324,11 @@ module RDF; module Util
|
|
324
324
|
content_type = format ? format.content_type.first : 'text/plain'
|
325
325
|
# Open as a file, passing any options
|
326
326
|
begin
|
327
|
-
|
327
|
+
url_no_frag_or_query = RDF::URI(filename_or_url)
|
328
|
+
url_no_frag_or_query.query = nil
|
329
|
+
url_no_frag_or_query.fragment = nil
|
330
|
+
options[:encoding] ||= Encoding::UTF_8
|
331
|
+
Kernel.open(url_no_frag_or_query, "r", options) do |file|
|
328
332
|
document_options = {
|
329
333
|
base_uri: filename_or_url.to_s,
|
330
334
|
charset: file.external_encoding.to_s,
|
@@ -421,15 +425,14 @@ module RDF; module Util
|
|
421
425
|
unless encoding.start_with?("utf")
|
422
426
|
body.force_encoding(Encoding::UTF_8)
|
423
427
|
encoding = "utf-8"
|
424
|
-
end
|
425
428
|
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
end
|
429
|
+
# Make sure Unicode is in NFC
|
430
|
+
begin
|
431
|
+
body.unicode_normalize! unless !body.unicode_normalized?
|
432
|
+
rescue Encoding::CompatibilityError
|
433
|
+
# Oh, well ...
|
434
|
+
end if body.respond_to?(:unicode_normalized?)
|
435
|
+
end
|
433
436
|
|
434
437
|
super(body, "r:#{encoding}")
|
435
438
|
end
|