firm 0.9.1
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 +7 -0
- data/.yardopts +12 -0
- data/LICENSE +21 -0
- data/README.md +209 -0
- data/lib/firm/serializable.rb +733 -0
- data/lib/firm/serialize/core.rb +43 -0
- data/lib/firm/serialize/id.rb +104 -0
- data/lib/firm/serializer/json.rb +394 -0
- data/lib/firm/serializer/xml.rb +544 -0
- data/lib/firm/serializer/yaml.rb +118 -0
- data/lib/firm/version.rb +9 -0
- data/lib/firm.rb +5 -0
- data/rakelib/yard/templates/default/fulldoc/html/css/firm.css +97 -0
- data/rakelib/yard/templates/default/fulldoc/html/setup.rb +25 -0
- data/rakelib/yard/templates/default/layout/html/setup.rb +5 -0
- data/rakelib/yard/yard/relative_markdown_links/version.rb +8 -0
- data/rakelib/yard/yard/relative_markdown_links.rb +39 -0
- data/rakelib/yard/yard-custom-templates.rb +2 -0
- data/rakelib/yard/yard-relative_markdown_links.rb +4 -0
- data/tests/serializer_tests.rb +945 -0
- data/tests/test_serialize.rb +8 -0
- data/tests/test_serialize_xml.rb +22 -0
- data/tests/test_serialize_yaml.rb +18 -0
- metadata +110 -0
@@ -0,0 +1,544 @@
|
|
1
|
+
# FIRM::Serializer - shape serializer module
|
2
|
+
# Copyright (c) M.J.N. Corino, The Netherlands
|
3
|
+
|
4
|
+
|
5
|
+
require 'set'
|
6
|
+
require 'ostruct'
|
7
|
+
require 'date'
|
8
|
+
|
9
|
+
module FIRM
|
10
|
+
|
11
|
+
module Serializable
|
12
|
+
|
13
|
+
if ::Object.const_defined?(:Nokogiri)
|
14
|
+
|
15
|
+
module XML
|
16
|
+
|
17
|
+
class << self
|
18
|
+
|
19
|
+
TLS_STATE_KEY = :firm_xml_state.freeze
|
20
|
+
private_constant :TLS_STATE_KEY
|
21
|
+
|
22
|
+
def xml_state
|
23
|
+
::Thread.current[TLS_STATE_KEY] ||= []
|
24
|
+
end
|
25
|
+
private :xml_state
|
26
|
+
|
27
|
+
def init_xml_load_state
|
28
|
+
xml_state.push({
|
29
|
+
allowed_classes: ::Set.new(Serializable.serializables.to_a.map(&:to_s))
|
30
|
+
})
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear_xml_load_state
|
34
|
+
xml_state.pop
|
35
|
+
end
|
36
|
+
|
37
|
+
def xml_tag_allowed?(xml)
|
38
|
+
xml.name != 'Object' || (xml.has_attribute?('class') && xml_state.last[:allowed_classes].include?(xml['class']))
|
39
|
+
end
|
40
|
+
private :xml_tag_allowed?
|
41
|
+
|
42
|
+
class NullHandler
|
43
|
+
def initialize(tag)
|
44
|
+
@tag = tag
|
45
|
+
end
|
46
|
+
def to_xml(_, _)
|
47
|
+
raise Serializable::Exception, "Missing XML handler for #{@tag}"
|
48
|
+
end
|
49
|
+
def from_xml(_)
|
50
|
+
raise Serializable::Exception, "Missing XML handler for #{@tag}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
private_constant :NullHandler
|
54
|
+
|
55
|
+
module HandlerMethods
|
56
|
+
def create_type_node(xml)
|
57
|
+
xml.add_child(Nokogiri::XML::Node.new(tag.to_s, xml.document))
|
58
|
+
end
|
59
|
+
def to_xml(_, _)
|
60
|
+
raise Serializable::Exception, "Missing serialization method for #{klass} XML handler"
|
61
|
+
end
|
62
|
+
def from_xml(xml)
|
63
|
+
raise Serializable::Exception, "Missing serialization method for #{klass} XML handler"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
private_constant :HandlerMethods
|
67
|
+
|
68
|
+
def xml_handlers
|
69
|
+
@xml_handlers ||= {}
|
70
|
+
end
|
71
|
+
private :xml_handlers
|
72
|
+
|
73
|
+
def register_xml_handler(handler)
|
74
|
+
raise RuntimeError, "Duplicate XML handler for tag #{handler.tag}" if xml_handlers.has_key?(handler.tag.to_s)
|
75
|
+
xml_handlers[handler.tag.to_s] = handler
|
76
|
+
end
|
77
|
+
private :register_xml_handler
|
78
|
+
|
79
|
+
def get_xml_handler(tag_or_value)
|
80
|
+
h = xml_handlers[tag_or_value.to_s]
|
81
|
+
unless h
|
82
|
+
tag_or_value = ::Object.const_get(tag_or_value.to_s) unless tag_or_value.is_a?(::Class)
|
83
|
+
h = xml_handlers.values.find { |hnd| hnd.klass > tag_or_value } || NullHandler.new(tag_or_value)
|
84
|
+
end
|
85
|
+
h
|
86
|
+
end
|
87
|
+
private :get_xml_handler
|
88
|
+
|
89
|
+
def define_xml_handler(klass, tag=nil, &block)
|
90
|
+
hnd_klass = Class.new
|
91
|
+
hnd_klass.singleton_class.include(HandlerMethods)
|
92
|
+
tag_code = if tag
|
93
|
+
::Symbol === tag ? ":#{tag}" : "'#{tag.to_s}'"
|
94
|
+
else
|
95
|
+
'klass'
|
96
|
+
end
|
97
|
+
hnd_klass.singleton_class.class_eval <<~__CODE
|
98
|
+
def klass; #{klass}; end
|
99
|
+
def tag; #{tag_code}; end
|
100
|
+
__CODE
|
101
|
+
hnd_klass.singleton_class.class_eval &block if block_given?
|
102
|
+
register_xml_handler(hnd_klass)
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_xml(xml, value)
|
106
|
+
if Serializable === value
|
107
|
+
value.to_xml(xml)
|
108
|
+
else
|
109
|
+
hk = case value
|
110
|
+
when true, false
|
111
|
+
value.to_s
|
112
|
+
else
|
113
|
+
value ? value.class : 'nil'
|
114
|
+
end
|
115
|
+
get_xml_handler(hk).to_xml(xml, value)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def from_xml(xml)
|
120
|
+
raise Serializable::Exception, "Illegal XML tag #{xml.name}" unless xml_tag_allowed?(xml)
|
121
|
+
get_xml_handler(xml.name).from_xml(xml)
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
define_xml_handler(FIRM::Serializable, :Object) do
|
127
|
+
def to_xml(_, _)
|
128
|
+
raise Serializable::Exception, 'Unsupported Object serialization'
|
129
|
+
end
|
130
|
+
def from_xml(xml)
|
131
|
+
raise Serializable::Exception, 'Missing Serializable class name' unless xml.has_attribute?('class')
|
132
|
+
Object.const_get(xml['class']).from_xml(xml)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
define_xml_handler(::NilClass, :nil) do
|
137
|
+
def to_xml(xml, _value)
|
138
|
+
create_type_node(xml)
|
139
|
+
xml
|
140
|
+
end
|
141
|
+
def from_xml(_xml)
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
define_xml_handler(::TrueClass, :true) do
|
147
|
+
def to_xml(xml, _value)
|
148
|
+
create_type_node(xml)
|
149
|
+
xml
|
150
|
+
end
|
151
|
+
def from_xml(_xml)
|
152
|
+
true
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
define_xml_handler(::FalseClass, :false) do
|
157
|
+
def to_xml(xml, _value)
|
158
|
+
create_type_node(xml)
|
159
|
+
xml
|
160
|
+
end
|
161
|
+
def from_xml(_xml)
|
162
|
+
false
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
define_xml_handler(::Array) do
|
167
|
+
def to_xml(xml, value)
|
168
|
+
node = create_type_node(xml)
|
169
|
+
value.each do |v|
|
170
|
+
Serializable::XML.to_xml(node, v)
|
171
|
+
end
|
172
|
+
xml
|
173
|
+
end
|
174
|
+
def from_xml(xml)
|
175
|
+
xml.elements.collect { |child| Serializable::XML.from_xml(child) }
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
define_xml_handler(::String) do
|
180
|
+
def to_xml(xml, value)
|
181
|
+
create_type_node(xml).add_child(Nokogiri::XML::CDATA.new(xml.document, value))
|
182
|
+
xml
|
183
|
+
end
|
184
|
+
def from_xml(xml)
|
185
|
+
xml.content
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
define_xml_handler(::Symbol) do
|
190
|
+
def to_xml(xml, value)
|
191
|
+
create_type_node(xml).content = value.to_s
|
192
|
+
xml
|
193
|
+
end
|
194
|
+
def from_xml(xml)
|
195
|
+
xml.content.to_sym
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
define_xml_handler(::Integer) do
|
200
|
+
def to_xml(xml, value)
|
201
|
+
create_type_node(xml).content = value.to_s
|
202
|
+
xml
|
203
|
+
end
|
204
|
+
def from_xml(xml)
|
205
|
+
Integer(xml.content)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
define_xml_handler(::Float) do
|
210
|
+
def to_xml(xml, value)
|
211
|
+
create_type_node(xml).add_child(Nokogiri::XML::CDATA.new(xml.document, value.to_s))
|
212
|
+
xml
|
213
|
+
end
|
214
|
+
def from_xml(xml)
|
215
|
+
case (s = xml.content)
|
216
|
+
when 'NaN' then :Float::NAN
|
217
|
+
when 'Infinity' then ::Float::INFINITY
|
218
|
+
when '-Infinity' then -::Float::INFINITY
|
219
|
+
else
|
220
|
+
Float(s)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
define_xml_handler(::Hash) do
|
226
|
+
def to_xml(xml, value)
|
227
|
+
node = create_type_node(xml)
|
228
|
+
value.each_pair do |k,v|
|
229
|
+
pair = node.add_child(Nokogiri::XML::Node.new('P', node.document))
|
230
|
+
Serializable::XML.to_xml(pair, k)
|
231
|
+
Serializable::XML.to_xml(pair, v)
|
232
|
+
end
|
233
|
+
xml
|
234
|
+
end
|
235
|
+
def from_xml(xml)
|
236
|
+
xml.elements.inject({}) do |hash, pair|
|
237
|
+
k, v = pair.elements
|
238
|
+
hash[Serializable::XML.from_xml(k)] = Serializable::XML.from_xml(v)
|
239
|
+
hash
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
define_xml_handler(::Struct) do
|
245
|
+
def to_xml(xml, value)
|
246
|
+
node = create_type_node(xml)
|
247
|
+
node['class'] = value.class.name
|
248
|
+
if (anchor = Serializable::Aliasing.get_anchor(value))
|
249
|
+
anchor_data = Serializable::Aliasing.get_anchor_data(value)
|
250
|
+
# retroactively insert the anchor in the anchored instance's serialization data
|
251
|
+
anchor_data['anchor'] = anchor unless anchor_data.has_attribute?('anchor')
|
252
|
+
node['alias'] = "#{anchor}"
|
253
|
+
else
|
254
|
+
# register anchor object **before** serializing properties to properly handle cycling (bidirectional
|
255
|
+
# references)
|
256
|
+
Serializable::Aliasing.register_anchor_object(value, node)
|
257
|
+
value.each do |v|
|
258
|
+
Serializable::XML.to_xml(node, v)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
xml
|
262
|
+
end
|
263
|
+
def from_xml(xml)
|
264
|
+
# deserializing alias
|
265
|
+
klass = ::Object.const_get(xml['class'])
|
266
|
+
if xml.has_attribute?('alias')
|
267
|
+
Serializable::Aliasing.resolve_anchor(klass, xml['alias'].to_i)
|
268
|
+
else
|
269
|
+
instance = klass.allocate
|
270
|
+
# in case this is an anchor restore the anchor instance before restoring the member values
|
271
|
+
# and afterwards initialize the instance with the restored member values
|
272
|
+
Serializable::Aliasing.restore_anchor(xml['anchor'].to_i, instance) if xml.has_attribute?('anchor')
|
273
|
+
instance.__send__(:initialize, *xml.elements.collect { |child| Serializable::XML.from_xml(child) })
|
274
|
+
instance
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
define_xml_handler(::Rational) do
|
280
|
+
def to_xml(xml, value)
|
281
|
+
node = create_type_node(xml)
|
282
|
+
Serializable::XML.to_xml(node, value.numerator)
|
283
|
+
Serializable::XML.to_xml(node, value.denominator)
|
284
|
+
xml
|
285
|
+
end
|
286
|
+
def from_xml(xml)
|
287
|
+
Rational(*xml.elements.collect { |child| Serializable::XML.from_xml(child) })
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
define_xml_handler(::Complex) do
|
292
|
+
def to_xml(xml, value)
|
293
|
+
node = create_type_node(xml)
|
294
|
+
Serializable::XML.to_xml(node, value.real)
|
295
|
+
Serializable::XML.to_xml(node, value.imaginary)
|
296
|
+
xml
|
297
|
+
end
|
298
|
+
def from_xml(xml)
|
299
|
+
Complex(*xml.elements.collect { |child| Serializable::XML.from_xml(child) })
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
if ::Object.const_defined?(:BigDecimal)
|
304
|
+
define_xml_handler(::BigDecimal) do
|
305
|
+
def to_xml(xml, value)
|
306
|
+
create_type_node(xml).add_child(Nokogiri::XML::CDATA.new(xml.document, value._dump))
|
307
|
+
xml
|
308
|
+
end
|
309
|
+
def from_xml(xml)
|
310
|
+
::BigDecimal._load(xml.content)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
define_xml_handler(::Range) do
|
316
|
+
def to_xml(xml, value)
|
317
|
+
node = create_type_node(xml)
|
318
|
+
Serializable::XML.to_xml(node, value.begin)
|
319
|
+
Serializable::XML.to_xml(node, value.end)
|
320
|
+
Serializable::XML.to_xml(node, value.exclude_end?)
|
321
|
+
xml
|
322
|
+
end
|
323
|
+
def from_xml(xml)
|
324
|
+
::Range.new(*xml.elements.collect { |child| Serializable::XML.from_xml(child) })
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
define_xml_handler(::Regexp) do
|
329
|
+
def to_xml(xml, value)
|
330
|
+
node = create_type_node(xml)
|
331
|
+
Serializable::XML.to_xml(node, value.source)
|
332
|
+
Serializable::XML.to_xml(node, value.options)
|
333
|
+
xml
|
334
|
+
end
|
335
|
+
def from_xml(xml)
|
336
|
+
::Regexp.new(*xml.elements.collect { |child| Serializable::XML.from_xml(child) })
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
define_xml_handler(::Time) do
|
341
|
+
def to_xml(xml, value)
|
342
|
+
node = create_type_node(xml)
|
343
|
+
utc = value.getutc
|
344
|
+
Serializable::XML.to_xml(node, utc.tv_sec)
|
345
|
+
Serializable::XML.to_xml(node, utc.tv_nsec)
|
346
|
+
xml
|
347
|
+
end
|
348
|
+
def from_xml(xml)
|
349
|
+
::Time.at(*xml.elements.collect { |child| Serializable::XML.from_xml(child) }, :nanosecond)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
define_xml_handler(::Date) do
|
354
|
+
def to_xml(xml, value)
|
355
|
+
node = create_type_node(xml)
|
356
|
+
idt = value.italy
|
357
|
+
Serializable::XML.to_xml(node, idt.year)
|
358
|
+
Serializable::XML.to_xml(node, idt.month)
|
359
|
+
Serializable::XML.to_xml(node, idt.day)
|
360
|
+
xml
|
361
|
+
end
|
362
|
+
def from_xml(xml)
|
363
|
+
::Date.new(*xml.elements.collect { |child| Serializable::XML.from_xml(child) }, ::Date::ITALY)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
define_xml_handler(::DateTime) do
|
368
|
+
def to_xml(xml, value)
|
369
|
+
node = create_type_node(xml)
|
370
|
+
idt = value.italy
|
371
|
+
Serializable::XML.to_xml(node, idt.year)
|
372
|
+
Serializable::XML.to_xml(node, idt.month)
|
373
|
+
Serializable::XML.to_xml(node, idt.day)
|
374
|
+
Serializable::XML.to_xml(node, idt.hour)
|
375
|
+
Serializable::XML.to_xml(node, idt.min)
|
376
|
+
Serializable::XML.to_xml(node, idt.sec_fraction.to_f + idt.sec)
|
377
|
+
Serializable::XML.to_xml(node, idt.offset)
|
378
|
+
xml
|
379
|
+
end
|
380
|
+
def from_xml(xml)
|
381
|
+
::DateTime.new(*xml.elements.collect { |child| Serializable::XML.from_xml(child) }, ::Date::ITALY)
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
define_xml_handler(::Set) do
|
386
|
+
def to_xml(xml, value)
|
387
|
+
node = create_type_node(xml)
|
388
|
+
value.each do |v|
|
389
|
+
Serializable::XML.to_xml(node, v)
|
390
|
+
end
|
391
|
+
xml
|
392
|
+
end
|
393
|
+
def from_xml(xml)
|
394
|
+
::Set.new(xml.elements.collect { |child| Serializable::XML.from_xml(child) })
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
define_xml_handler(::OpenStruct) do
|
399
|
+
def to_xml(xml, value)
|
400
|
+
node = create_type_node(xml)
|
401
|
+
value.each_pair do |k,v|
|
402
|
+
pair = node.add_child(Nokogiri::XML::Node.new('P', node.document))
|
403
|
+
Serializable::XML.to_xml(pair, k)
|
404
|
+
Serializable::XML.to_xml(pair, v)
|
405
|
+
end
|
406
|
+
xml
|
407
|
+
end
|
408
|
+
def from_xml(xml)
|
409
|
+
xml.elements.inject(::OpenStruct.new) do |hash, pair|
|
410
|
+
k, v = pair.elements
|
411
|
+
hash[Serializable::XML.from_xml(k)] = Serializable::XML.from_xml(v)
|
412
|
+
hash
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
class HashAdapter
|
418
|
+
def initialize(xml)
|
419
|
+
@xml = xml
|
420
|
+
end
|
421
|
+
|
422
|
+
def has_key?(id)
|
423
|
+
!!@xml.at_xpath(id.to_s)
|
424
|
+
end
|
425
|
+
|
426
|
+
def [](id)
|
427
|
+
node = @xml.at_xpath(id.to_s)
|
428
|
+
node = node ? node.first_element_child : nil
|
429
|
+
node ? Serializable::XML.from_xml(node) : nil
|
430
|
+
end
|
431
|
+
|
432
|
+
def []=(id, value)
|
433
|
+
Serializable::XML.to_xml(@xml.add_child(Nokogiri::XML::Node.new(id.to_s, @xml.document)), value)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
def self.dump(obj, io=nil, pretty: false)
|
438
|
+
begin
|
439
|
+
# initialize anchor registry
|
440
|
+
Serializable::Aliasing.start_anchor_object_registry
|
441
|
+
# generate XML document
|
442
|
+
xml = to_xml(Nokogiri::XML::Document.new, obj)
|
443
|
+
opts = pretty ? { indent: 2 } : { save_with: 0 }
|
444
|
+
if io || io.respond_to?(:write)
|
445
|
+
xml.write_xml_to(io, opts)
|
446
|
+
io
|
447
|
+
else
|
448
|
+
xml.to_xml(opts)
|
449
|
+
end
|
450
|
+
ensure
|
451
|
+
# reset anchor registry
|
452
|
+
Serializable::Aliasing.clear_anchor_object_registry
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
def self.load(source)
|
457
|
+
xml = Nokogiri::XML(source)
|
458
|
+
return nil unless xml
|
459
|
+
begin
|
460
|
+
# initialize alias anchor restoration map
|
461
|
+
Serializable::Aliasing.start_anchor_references
|
462
|
+
# initialize XML loader state
|
463
|
+
Serializable::XML.init_xml_load_state
|
464
|
+
# load from xml doc
|
465
|
+
xml.root ? Serializable::XML.from_xml(xml.root) : nil
|
466
|
+
ensure
|
467
|
+
# reset XML loader state
|
468
|
+
Serializable::XML.clear_xml_load_state
|
469
|
+
# reset alias anchor restoration map
|
470
|
+
Serializable::Aliasing.clear_anchor_references
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
# extend serialization class methods
|
475
|
+
module SerializeClassMethods
|
476
|
+
|
477
|
+
def from_xml(xml)
|
478
|
+
data = XML::HashAdapter.new(xml)
|
479
|
+
# deserializing alias
|
480
|
+
if xml.has_attribute?('alias')
|
481
|
+
Serializable::Aliasing.resolve_anchor(self, xml['alias'].to_i)
|
482
|
+
else
|
483
|
+
instance = self.allocate
|
484
|
+
Serializable::Aliasing.restore_anchor(xml['anchor'].to_i, instance) if xml.has_attribute?('anchor')
|
485
|
+
instance.__send__(:init_from_serialized, data)
|
486
|
+
.__send__(:from_serialized, data)
|
487
|
+
.__send__(:finalize_from_serialized)
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
end
|
492
|
+
|
493
|
+
# extend instance serialization methods
|
494
|
+
module SerializeInstanceMethods
|
495
|
+
|
496
|
+
def to_xml(xml)
|
497
|
+
node = xml.add_child(Nokogiri::XML::Node.new('Object', xml.document))
|
498
|
+
node['class'] = self.class.name
|
499
|
+
if (anchor = Serializable::Aliasing.get_anchor(self))
|
500
|
+
anchor_data = Serializable::Aliasing.get_anchor_data(self)
|
501
|
+
# retroactively insert the anchor in the anchored instance's serialization data
|
502
|
+
anchor_data['anchor'] = anchor unless anchor_data.has_attribute?('anchor')
|
503
|
+
node['alias'] = "#{anchor}"
|
504
|
+
else
|
505
|
+
# register anchor object **before** serializing properties to properly handle cycling (bidirectional
|
506
|
+
# references)
|
507
|
+
Serializable::Aliasing.register_anchor_object(self, node)
|
508
|
+
for_serialize(XML::HashAdapter.new(node))
|
509
|
+
end
|
510
|
+
xml
|
511
|
+
end
|
512
|
+
|
513
|
+
end
|
514
|
+
|
515
|
+
end
|
516
|
+
|
517
|
+
# extend serialization class methods
|
518
|
+
module SerializeClassMethods
|
519
|
+
|
520
|
+
include XML::SerializeClassMethods
|
521
|
+
|
522
|
+
end
|
523
|
+
|
524
|
+
# extend instance serialization methods
|
525
|
+
module SerializeInstanceMethods
|
526
|
+
|
527
|
+
include XML::SerializeInstanceMethods
|
528
|
+
|
529
|
+
end
|
530
|
+
|
531
|
+
class ID
|
532
|
+
include XML::SerializeInstanceMethods
|
533
|
+
class << self
|
534
|
+
include XML::SerializeClassMethods
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
register(:xml, XML)
|
539
|
+
|
540
|
+
end
|
541
|
+
|
542
|
+
end
|
543
|
+
|
544
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# FIRM::Serializer - shape serializer module
|
2
|
+
# Copyright (c) M.J.N. Corino, The Netherlands
|
3
|
+
|
4
|
+
|
5
|
+
require 'yaml'
|
6
|
+
require 'date'
|
7
|
+
require 'set'
|
8
|
+
require 'ostruct'
|
9
|
+
|
10
|
+
module FIRM
|
11
|
+
|
12
|
+
module Serializable
|
13
|
+
|
14
|
+
module YAML
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def serializables
|
18
|
+
list = [::Date, ::DateTime, ::Range, ::Rational, ::Complex, ::Regexp, ::Struct, ::Symbol, ::Time, ::Set, ::OpenStruct]
|
19
|
+
list.push(::BigDecimal) if ::Object.const_defined?(:BigDecimal)
|
20
|
+
list
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module YamlSerializePatch
|
25
|
+
|
26
|
+
ALLOWED_ALIASES = [Serializable::ID]
|
27
|
+
|
28
|
+
if ::RUBY_VERSION >= '3.1.0'
|
29
|
+
def revive(klass, node)
|
30
|
+
if FIRM::Serializable > klass
|
31
|
+
s = register(node, klass.allocate)
|
32
|
+
s.__send__(:init_from_serialized, data = revive_hash({}, node, true))
|
33
|
+
init_with(s, data, node)
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
else
|
39
|
+
def revive(klass, node)
|
40
|
+
if FIRM::Serializable > klass
|
41
|
+
s = register(node, klass.allocate)
|
42
|
+
s.__send__(:init_from_serialized, data = revive_hash({}, node))
|
43
|
+
init_with(s, data, node)
|
44
|
+
else
|
45
|
+
super
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class RestrictedRelaxed < ::YAML::ClassLoader
|
52
|
+
def initialize(classes)
|
53
|
+
@classes = classes
|
54
|
+
@allow_struct = @classes.include?('Struct')
|
55
|
+
super()
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def find(klassname)
|
61
|
+
if @classes.include?(klassname)
|
62
|
+
super
|
63
|
+
elsif @allow_struct && ::Struct > super
|
64
|
+
@cache[klassname]
|
65
|
+
else
|
66
|
+
raise ::YAML::DisallowedClass.new('load', klassname)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.dump(obj, io=nil, **)
|
72
|
+
::YAML.dump(obj, io)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.load(source)
|
76
|
+
result = ::YAML.parse(source, filename: nil)
|
77
|
+
return nil unless result
|
78
|
+
allowed_classes =(YAML.serializables + Serializable.serializables.to_a).map(&:to_s)
|
79
|
+
class_loader = RestrictedRelaxed.new(allowed_classes)
|
80
|
+
scanner = ::YAML::ScalarScanner.new(class_loader)
|
81
|
+
visitor = ::YAML::Visitors::ToRuby.new(scanner, class_loader)
|
82
|
+
visitor.extend(YamlSerializePatch)
|
83
|
+
visitor.accept result
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
# extend instance serialization methods
|
89
|
+
module SerializeInstanceMethods
|
90
|
+
|
91
|
+
def encode_with(coder)
|
92
|
+
for_serialize(coder)
|
93
|
+
end
|
94
|
+
|
95
|
+
def init_with(coder)
|
96
|
+
from_serialized(coder.map)
|
97
|
+
finalize_from_serialized
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
class ID
|
103
|
+
|
104
|
+
def encode_with(coder)
|
105
|
+
for_serialize(coder)
|
106
|
+
end
|
107
|
+
|
108
|
+
def init_with(_coder)
|
109
|
+
# noop
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
register(:yaml, YAML)
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
data/lib/firm/version.rb
ADDED