firm 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,9 @@
1
+ # FIRM - multiple output ruby object serializer<br>
2
+ # Copyright (c) M.J.N. Corino, The Netherlands
3
+
4
+ module FIRM
5
+
6
+ # FIRM version
7
+ VERSION = "0.9.1"
8
+
9
+ end
data/lib/firm.rb ADDED
@@ -0,0 +1,5 @@
1
+ # FIRM - multiple output ruby object serializer
2
+ # Copyright (c) M.J.N. Corino, The Netherlands
3
+
4
+ require 'firm/version'
5
+ require 'firm/serializable'