shale 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c96481848145b9736af4cc830c84c04970042fdc61f7d348aa64099065319ef
4
- data.tar.gz: 25739ce30a7f4e66040b5dbff394da06a5c578065af0cd0b03bd0a56c7d78cbf
3
+ metadata.gz: ab65ef1fa452cb7d3810b6a996597190541fcbc5e4d5832901f8608bb8ddbf29
4
+ data.tar.gz: 241f5a1b333205a53cb598e51c8cd639a82f6f7661437f44d20fc3a0674935df
5
5
  SHA512:
6
- metadata.gz: f205c1407868c6469922b7553f9aef3cdb0dc70502981d56eeef5b08703c793213f63c2a898568f372bc4e9519bcbfa771eefd6cfd663746e5b4180d1738c351
7
- data.tar.gz: 95d7e2c6f88d7d5099c1ff62345509e83faee23a3bdd16e77d8eea05fb061e0eeb76fabbba5dc50d7c15ea675d2d1430570a2ac4134416c3d03d905d4ef59528
6
+ metadata.gz: a6107d0c547481b76ad03573a880001043b030825eccccdb11e5627dfc817467bedce0c77f0c2ca52f267a2791855c5b37aa6a518347be908458bbeff42e1f50
7
+ data.tar.gz: c7e883f3a41049a72904ca94702eadcb2c568b1d8c7717ad2000340b21c53b5f9c90bd1a981e57866bb704e9ddcfb36d713b74061ed59eb3d065df9fbc5fbcba
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## [0.2.0] - 2022-01-20
2
+
3
+ ### Added
4
+ - Support for using methods to extract/generate data from/to document
5
+
6
+ ### Changed
7
+ - deduplicate code
8
+ - Rename `Shale::Type::Complex` -> `Shale::Type::Composite`
9
+
1
10
  ## [0.1.0] - 2021-11-30
2
11
 
3
12
  First public release
data/README.md CHANGED
@@ -48,7 +48,7 @@ end
48
48
  ```
49
49
 
50
50
  - `default: -> { 'value' }` - add a default value to attribute (it must be a proc that returns value)
51
- - `collection: true` - indicated that a attribute is a collection
51
+ - `collection: true` - indicates that a attribute is a collection
52
52
 
53
53
  ### Creating objects
54
54
 
@@ -327,6 +327,100 @@ DATA
327
327
  - `map_attribute` - map element's attribute to attribute
328
328
  - `map_content` - map first text node to attribute
329
329
 
330
+ ### Using methods to extract and generate data
331
+
332
+ If you need full controll over extracting and generating data from/to document,
333
+ you can use methods to do so:
334
+
335
+ ```ruby
336
+ class Person < Shale::Mapper
337
+ attribute :hobbies, Shale::Type::String, collection: true
338
+ attribute :street, Shale::Type::String
339
+ attribute :city, Shale::Type::String
340
+
341
+ json do
342
+ map 'hobbies', using: { from: :hobbies_from_json, to: :hobbies_to_json }
343
+ map 'address', using: { from: :address_from_json, to: :address_to_json }
344
+ end
345
+
346
+ xml do
347
+ root 'Person'
348
+
349
+ map_attribute 'hobbies', using: { from: :hobbies_from_xml, to: :hobbies_to_xml }
350
+ map_element 'Address', using: { from: :address_from_xml, to: :address_to_xml }
351
+ end
352
+
353
+ def hobbies_from_json(value)
354
+ self.hobbies = value.split(',').map(&:strip)
355
+ end
356
+
357
+ def hobbies_to_json
358
+ hobbies.join(', ')
359
+ end
360
+
361
+ def address_from_json(value)
362
+ self.street = value['street']
363
+ self.city = value['city']
364
+ end
365
+
366
+ def address_to_json
367
+ { 'street' => street, 'city' => city }
368
+ end
369
+
370
+ def hobbies_from_xml(value)
371
+ self.hobbies = value.split(',').map(&:strip)
372
+ end
373
+
374
+ def hobbies_to_xml(element, doc)
375
+ doc.add_attribute(element, 'hobbies', hobbies.join(', '))
376
+ end
377
+
378
+ def address_from_xml(node)
379
+ self.street = node.children.find { |e| e.name == 'Street' }.text
380
+ self.city = node.children.find { |e| e.name == 'City' }.text
381
+ end
382
+
383
+ def address_to_xml(parent, doc)
384
+ street_element = doc.create_element('Street')
385
+ doc.add_text(street_element, street.to_s)
386
+
387
+ city_element = doc.create_element('City')
388
+ doc.add_text(city_element, city.to_s)
389
+
390
+ address_element = doc.create_element('Address')
391
+ doc.add_element(address_element, street_element)
392
+ doc.add_element(address_element, city_element)
393
+ doc.add_element(parent, address_element)
394
+ end
395
+ end
396
+
397
+ person = Person.from_json(<<~DATA)
398
+ {
399
+ "hobbies": "Singing, Dancing, Running",
400
+ "address": {
401
+ "street": "Oxford Street",
402
+ "city": "London"
403
+ }
404
+ }
405
+ DATA
406
+
407
+ person = Person.from_xml(<<~DATA)
408
+ <Person hobbies="Singing, Dancing, Running">
409
+ <Address>
410
+ <Street>Oxford Street</Street>
411
+ <City>London</City>
412
+ </Address>
413
+ </person>
414
+ DATA
415
+
416
+ # =>
417
+ #
418
+ # #<Person:0x00007f9bc3086d60
419
+ # @hobbies=["Singing", "Dancing", "Running"],
420
+ # @street="Oxford Street",
421
+ # @city="London">
422
+ ```
423
+
330
424
  ### Supported types
331
425
 
332
426
  Shale supports these types out of the box:
@@ -355,10 +449,10 @@ end
355
449
  ### Adapters
356
450
 
357
451
  Shale uses adapters for parsing and generating documents.
358
- By default Ruby's standard JSON parser is used for handing JSON documents, YAML for YAML and
452
+ By default Ruby's standard JSON parser is used for handling JSON documents, YAML for YAML and
359
453
  REXML for XML.
360
454
 
361
- You can change it by providing your own adapter. For JSON and YAML adapter must implement
455
+ You can change it by providing your own adapter. For JSON and YAML, adapter must implement
362
456
  `.load` and `.dump` class methods.
363
457
 
364
458
  ```ruby
@@ -369,15 +463,22 @@ Shale.json_adapter = MultiJson
369
463
  Shale.yaml_adapter = MyYamlAdapter
370
464
  ```
371
465
 
372
- For XML, other than REXML, Shale provides adapters for most popular Ruby XML parsers:
466
+ For XML, Shale provides adapters for most popular Ruby XML parsers:
373
467
 
374
468
  ```ruby
375
469
  require 'shale'
376
470
 
471
+ # REXML is used by default:
472
+
473
+ require 'shale/adapter/rexml'
474
+ Shale.xml_adapter = Shale::Adapter::REXML
475
+
476
+ # if you want to use Nokogiri:
477
+
377
478
  require 'shale/adapter/nokogiri'
378
479
  Shale.xml_adapter = Shale::Adapter::Nokogiri
379
480
 
380
- # or if you want to use Ox
481
+ # or if you want to use Ox:
381
482
 
382
483
  require 'shale/adapter/ox'
383
484
  Shale.xml_adapter = Shale::Adapter::Ox
data/lib/shale/error.rb CHANGED
@@ -30,4 +30,10 @@ module Shale
30
30
  super("'#{attribute}' default is not callable for #{record}.")
31
31
  end
32
32
  end
33
+
34
+ # Error for passing incorrect arguments to map functions
35
+ #
36
+ # @api private
37
+ class IncorrectMappingArgumentsError < StandardError
38
+ end
33
39
  end
data/lib/shale/mapper.rb CHANGED
@@ -5,7 +5,7 @@ require_relative 'error'
5
5
  require_relative 'utils'
6
6
  require_relative 'mapping/key_value'
7
7
  require_relative 'mapping/xml'
8
- require_relative 'type/complex'
8
+ require_relative 'type/composite'
9
9
 
10
10
  module Shale
11
11
  # Base class used for mapping
@@ -42,7 +42,7 @@ module Shale
42
42
  # person.to_json
43
43
  #
44
44
  # @api public
45
- class Mapper < Type::Complex
45
+ class Mapper < Type::Composite
46
46
  @attributes = {}
47
47
  @hash_mapping = Mapping::KeyValue.new
48
48
  @json_mapping = Mapping::KeyValue.new
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../error'
4
+
5
+ module Shale
6
+ module Mapping
7
+ class Base
8
+ private
9
+
10
+ # Validate correctness of argument passed to map functions
11
+ #
12
+ # @param [String] key
13
+ # @param [Symbol] to
14
+ # @param [Hash] using
15
+ #
16
+ # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
17
+ #
18
+ # @api private
19
+ def validate_arguments(key, to, using)
20
+ if to.nil? && using.nil?
21
+ msg = ":to or :using argument is required for mapping '#{key}'"
22
+ raise IncorrectMappingArgumentsError, msg
23
+ end
24
+
25
+ if !using.nil? && (using[:from].nil? || using[:to].nil?)
26
+ msg = ":using argument for mapping '#{key}' requires :to and :from keys"
27
+ raise IncorrectMappingArgumentsError, msg
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'base'
4
+
3
5
  module Shale
4
6
  module Mapping
5
7
  # Mapping for key/value serialization formats (Hash/JSON/YAML)
6
8
  #
7
9
  # @api private
8
- class KeyValue
10
+ class KeyValue < Base
9
11
  # Return keys mapping hash
10
12
  #
11
13
  # @return [Hash]
@@ -17,6 +19,7 @@ module Shale
17
19
  #
18
20
  # @api private
19
21
  def initialize
22
+ super
20
23
  @keys = {}
21
24
  end
22
25
 
@@ -25,9 +28,12 @@ module Shale
25
28
  # @param [String] key Document's key
26
29
  # @param [Symbol] to Object's attribute
27
30
  #
31
+ # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
32
+ #
28
33
  # @api private
29
- def map(key, to:)
30
- @keys[key] = to
34
+ def map(key, to: nil, using: nil)
35
+ validate_arguments(key, to, using)
36
+ @keys[key] = to || using
31
37
  end
32
38
 
33
39
  # @api private
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'base'
4
+
3
5
  module Shale
4
6
  module Mapping
5
- class Xml
7
+ class Xml < Base
6
8
  # Return elements mapping hash
7
9
  #
8
10
  # @return [Hash]
@@ -28,6 +30,7 @@ module Shale
28
30
  #
29
31
  # @api private
30
32
  def initialize
33
+ super
31
34
  @elements = {}
32
35
  @attributes = {}
33
36
  @content = nil
@@ -39,9 +42,12 @@ module Shale
39
42
  # @param [String] element Document's element
40
43
  # @param [Symbol] to Object's attribute
41
44
  #
45
+ # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
46
+ #
42
47
  # @api private
43
- def map_element(element, to:)
44
- @elements[element] = to
48
+ def map_element(element, to: nil, using: nil)
49
+ validate_arguments(element, to, using)
50
+ @elements[element] = to || using
45
51
  end
46
52
 
47
53
  # Map document's attribute to object's attribute
@@ -49,9 +55,12 @@ module Shale
49
55
  # @param [String] attribute Document's attribute
50
56
  # @param [Symbol] to Object's attribute
51
57
  #
58
+ # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
59
+ #
52
60
  # @api private
53
- def map_attribute(attribute, to:)
54
- @attributes[attribute] = to
61
+ def map_attribute(attribute, to: nil, using: nil)
62
+ validate_arguments(attribute, to, using)
63
+ @attributes[attribute] = to || using
55
64
  end
56
65
 
57
66
  # Map document's content to object's attribute
@@ -0,0 +1,326 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Shale
6
+ module Type
7
+ # Build composite object. Don't use it directly.
8
+ # It serves as a base type class for @see Shale::Mapper
9
+ #
10
+ # @api private
11
+ class Composite < Base
12
+ class << self
13
+ %i[hash json yaml].each do |format|
14
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
15
+ # Convert Hash to Object using Hash/JSON/YAML mapping
16
+ #
17
+ # @param [Hash] hash Hash to convert
18
+ #
19
+ # @return [Shale::Mapper]
20
+ #
21
+ # @api public
22
+ def out_of_#{format}(hash)
23
+ instance = new
24
+
25
+ mapping_keys = #{format}_mapping.keys
26
+
27
+ hash.each do |key, value|
28
+ mapping = mapping_keys[key]
29
+ next unless mapping
30
+
31
+ if mapping.is_a?(Hash)
32
+ instance.send(mapping[:from], value)
33
+ else
34
+ attribute = attributes[mapping]
35
+ next unless attribute
36
+
37
+ if value.nil?
38
+ instance.public_send("\#{attribute.name}=", nil)
39
+ next
40
+ end
41
+
42
+ if attribute.collection?
43
+ [*value].each do |val|
44
+ val = val ? attribute.type.out_of_#{format}(val) : val
45
+ instance.public_send(attribute.name) << attribute.type.cast(val)
46
+ end
47
+ else
48
+ val = attribute.type.out_of_#{format}(value)
49
+ instance.public_send("\#{attribute.name}=", val)
50
+ end
51
+ end
52
+ end
53
+
54
+ instance
55
+ end
56
+
57
+ # Convert Object to Hash using Hash/JSON/YAML mapping
58
+ #
59
+ # @param [Shale::Type::Base] instance Object to convert
60
+ #
61
+ # @return [Hash]
62
+ #
63
+ # @api public
64
+ def as_#{format}(instance)
65
+ hash = {}
66
+
67
+ instance.class.#{format}_mapping.keys.each do |key, attr|
68
+ if attr.is_a?(Hash)
69
+ hash[key] = instance.send(attr[:to])
70
+ else
71
+ attribute = instance.class.attributes[attr]
72
+ next unless attribute
73
+
74
+ value = instance.public_send(attribute.name)
75
+
76
+ if value.nil?
77
+ hash[key] = nil
78
+ next
79
+ end
80
+
81
+ if attribute.collection?
82
+ hash[key] = [*value].map { |v| v ? attribute.type.as_#{format}(v) : v }
83
+ else
84
+ hash[key] = attribute.type.as_#{format}(value)
85
+ end
86
+ end
87
+ end
88
+
89
+ hash
90
+ end
91
+ RUBY
92
+ end
93
+
94
+ alias from_hash out_of_hash
95
+
96
+ alias to_hash as_hash
97
+
98
+ # Convert JSON to Object
99
+ #
100
+ # @param [String] json JSON to convert
101
+ #
102
+ # @return [Shale::Mapper]
103
+ #
104
+ # @api public
105
+ def from_json(json)
106
+ out_of_json(Shale.json_adapter.load(json))
107
+ end
108
+
109
+ # Convert Object to JSON
110
+ #
111
+ # @param [Shale::Type::Base] instance Object to convert
112
+ #
113
+ # @return [String]
114
+ #
115
+ # @api public
116
+ def to_json(instance)
117
+ Shale.json_adapter.dump(as_json(instance))
118
+ end
119
+
120
+ # Convert YAML to Object
121
+ #
122
+ # @param [String] yaml YAML to convert
123
+ #
124
+ # @return [Shale::Mapper]
125
+ #
126
+ # @api public
127
+ def from_yaml(yaml)
128
+ out_of_yaml(Shale.yaml_adapter.load(yaml))
129
+ end
130
+
131
+ # Convert Object to YAML
132
+ #
133
+ # @param [Shale::Type::Base] instance Object to convert
134
+ #
135
+ # @return [String]
136
+ #
137
+ # @api public
138
+ def to_yaml(instance)
139
+ Shale.yaml_adapter.dump(as_yaml(instance))
140
+ end
141
+
142
+ # Convert XML document to Object
143
+ #
144
+ # @param [Shale::Adapter::<XML adapter>::Node] xml XML to convert
145
+ #
146
+ # @return [Shale::Mapper]
147
+ #
148
+ # @api public
149
+ def out_of_xml(element)
150
+ instance = new
151
+
152
+ element.attributes.each do |key, value|
153
+ mapping = xml_mapping.attributes[key.to_s]
154
+ next unless mapping
155
+
156
+ if mapping.is_a?(Hash)
157
+ instance.send(mapping[:from], value)
158
+ else
159
+ attribute = attributes[mapping]
160
+ next unless attribute
161
+
162
+ if attribute.collection?
163
+ instance.public_send(attribute.name) << attribute.type.cast(value)
164
+ else
165
+ instance.public_send("#{attribute.name}=", value)
166
+ end
167
+ end
168
+ end
169
+
170
+ if xml_mapping.content
171
+ attribute = attributes[xml_mapping.content]
172
+
173
+ if attribute
174
+ instance.public_send("#{attribute.name}=", attribute.type.out_of_xml(element))
175
+ end
176
+ end
177
+
178
+ element.children.each do |node|
179
+ mapping = xml_mapping.elements[node.name]
180
+ next unless mapping
181
+
182
+ if mapping.is_a?(Hash)
183
+ instance.send(mapping[:from], node)
184
+ else
185
+ attribute = attributes[mapping]
186
+ next unless attribute
187
+
188
+ if attribute.collection?
189
+ value = attribute.type.out_of_xml(node)
190
+ instance.public_send(attribute.name) << attribute.type.cast(value)
191
+ else
192
+ instance.public_send("#{attribute.name}=", attribute.type.out_of_xml(node))
193
+ end
194
+ end
195
+ end
196
+
197
+ instance
198
+ end
199
+
200
+ # Convert XML to Object
201
+ #
202
+ # @param [String] xml XML to convert
203
+ #
204
+ # @return [Shale::Mapper]
205
+ #
206
+ # @api public
207
+ def from_xml(xml)
208
+ out_of_xml(Shale.xml_adapter.load(xml))
209
+ end
210
+
211
+ # Convert Object to XML document
212
+ #
213
+ # @param [Shale::Type::Base] instance Object to convert
214
+ # @param [String, nil] node_name XML node name
215
+ # @param [Shale::Adapter::<xml adapter>::Document, nil] doc Object to convert
216
+ #
217
+ # @return [::REXML::Document, ::Nokogiri::Document, ::Ox::Document]
218
+ #
219
+ # @api public
220
+ def as_xml(instance, node_name = nil, doc = nil)
221
+ unless doc
222
+ doc = Shale.xml_adapter.create_document
223
+ doc.add_element(doc.doc, as_xml(instance, instance.class.xml_mapping.root, doc))
224
+ return doc.doc
225
+ end
226
+
227
+ element = doc.create_element(node_name)
228
+
229
+ xml_mapping.attributes.each do |xml_attr, obj_attr|
230
+ if obj_attr.is_a?(Hash)
231
+ instance.send(obj_attr[:to], element, doc)
232
+ else
233
+ attribute = instance.class.attributes[obj_attr]
234
+ next unless attribute
235
+
236
+ value = instance.public_send(attribute.name)
237
+
238
+ if value && !value.empty?
239
+ doc.add_attribute(element, xml_attr, value)
240
+ end
241
+ end
242
+ end
243
+
244
+ if xml_mapping.content
245
+ attribute = instance.class.attributes[xml_mapping.content]
246
+
247
+ if attribute
248
+ value = instance.public_send(attribute.name)
249
+ doc.add_text(element, value.to_s) if value
250
+ end
251
+ end
252
+
253
+ xml_mapping.elements.each do |xml_name, obj_attr|
254
+ if obj_attr.is_a?(Hash)
255
+ instance.send(obj_attr[:to], element, doc)
256
+ else
257
+ attribute = instance.class.attributes[obj_attr]
258
+ next unless attribute
259
+
260
+ value = instance.public_send(attribute.name)
261
+ next if value.nil?
262
+
263
+ if attribute.collection?
264
+ [*value].each do |v|
265
+ next if v.nil?
266
+ doc.add_element(element, attribute.type.as_xml(v, xml_name, doc))
267
+ end
268
+ else
269
+ doc.add_element(element, attribute.type.as_xml(value, xml_name, doc))
270
+ end
271
+ end
272
+ end
273
+
274
+ element
275
+ end
276
+
277
+ # Convert Object to XML
278
+ #
279
+ # @param [Shale::Type::Base] instance Object to convert
280
+ #
281
+ # @return [String]
282
+ #
283
+ # @api public
284
+ def to_xml(instance)
285
+ Shale.xml_adapter.dump(as_xml(instance))
286
+ end
287
+ end
288
+
289
+ # Convert Object to Hash
290
+ #
291
+ # @return [Hash]
292
+ #
293
+ # @api public
294
+ def to_hash
295
+ self.class.to_hash(self)
296
+ end
297
+
298
+ # Convert Object to JSON
299
+ #
300
+ # @return [String]
301
+ #
302
+ # @api public
303
+ def to_json
304
+ self.class.to_json(self)
305
+ end
306
+
307
+ # Convert Object to YAML
308
+ #
309
+ # @return [String]
310
+ #
311
+ # @api public
312
+ def to_yaml
313
+ self.class.to_yaml(self)
314
+ end
315
+
316
+ # Convert Object to XML
317
+ #
318
+ # @return [String]
319
+ #
320
+ # @api public
321
+ def to_xml
322
+ self.class.to_xml(self)
323
+ end
324
+ end
325
+ end
326
+ end
data/lib/shale/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Shale
4
4
  # @api private
5
- VERSION = '0.1.0'
5
+ VERSION = '0.2.0'
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shale
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kamil Giszczak
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-30 00:00:00.000000000 Z
11
+ date: 2022-01-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Ruby object mapper and serializer for XML, JSON and YAML.
14
14
  email:
@@ -28,11 +28,12 @@ files:
28
28
  - lib/shale/attribute.rb
29
29
  - lib/shale/error.rb
30
30
  - lib/shale/mapper.rb
31
+ - lib/shale/mapping/base.rb
31
32
  - lib/shale/mapping/key_value.rb
32
33
  - lib/shale/mapping/xml.rb
33
34
  - lib/shale/type/base.rb
34
35
  - lib/shale/type/boolean.rb
35
- - lib/shale/type/complex.rb
36
+ - lib/shale/type/composite.rb
36
37
  - lib/shale/type/date.rb
37
38
  - lib/shale/type/float.rb
38
39
  - lib/shale/type/integer.rb
@@ -1,427 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'base'
4
-
5
- module Shale
6
- module Type
7
- # Build complex object. Don't use it directly.
8
- # It serves as a base type class for @see Shale::Mapper
9
- #
10
- # @api private
11
- class Complex < Base
12
- class << self
13
- # Convert Hash to Object
14
- #
15
- # @param [Hash] hash Hash to convert
16
- #
17
- # @return [Shale::Mapper]
18
- #
19
- # @api public
20
- def out_of_hash(hash)
21
- instance = new
22
-
23
- hash.each do |key, value|
24
- mapping = hash_mapping.keys[key]
25
- next unless mapping
26
-
27
- attribute = attributes[mapping]
28
- next unless attribute
29
-
30
- if value.nil?
31
- instance.public_send("#{attribute.name}=", nil)
32
- next
33
- end
34
-
35
- if attribute.collection?
36
- [*value].each do |val|
37
- val = val ? attribute.type.out_of_hash(val) : val
38
- instance.public_send(attribute.name) << attribute.type.cast(val)
39
- end
40
- else
41
- instance.public_send("#{attribute.name}=", attribute.type.out_of_hash(value))
42
- end
43
- end
44
-
45
- instance
46
- end
47
-
48
- alias from_hash out_of_hash
49
-
50
- # Convert Object to Hash
51
- #
52
- # @param [Shale::Type::Base] instance Object to convert
53
- #
54
- # @return [Hash]
55
- #
56
- # @api public
57
- def as_hash(instance)
58
- hash = {}
59
-
60
- instance.class.hash_mapping.keys.each do |key, attr|
61
- attribute = instance.class.attributes[attr]
62
- next unless attribute
63
-
64
- value = instance.public_send(attribute.name)
65
-
66
- if value.nil?
67
- hash[key] = nil
68
- next
69
- end
70
-
71
- if attribute.collection?
72
- hash[key] = [*value].map { |v| v ? attribute.type.as_hash(v) : v }
73
- else
74
- hash[key] = attribute.type.as_hash(value)
75
- end
76
- end
77
-
78
- hash
79
- end
80
-
81
- alias to_hash as_hash
82
-
83
- # Convert JSON document to Object
84
- #
85
- # @param [Hash] hash JSON document to convert
86
- #
87
- # @return [Shale::Mapper]
88
- #
89
- # @api public
90
- def out_of_json(hash)
91
- instance = new
92
-
93
- hash.each do |key, value|
94
- mapping = json_mapping.keys[key]
95
- next unless mapping
96
-
97
- attribute = attributes[mapping]
98
- next unless attribute
99
-
100
- if value.nil?
101
- instance.public_send("#{attribute.name}=", nil)
102
- next
103
- end
104
-
105
- if attribute.collection?
106
- [*value].each do |val|
107
- val = val ? attribute.type.out_of_json(val) : val
108
- instance.public_send(attribute.name) << attribute.type.cast(val)
109
- end
110
- else
111
- instance.public_send("#{attribute.name}=", attribute.type.out_of_json(value))
112
- end
113
- end
114
-
115
- instance
116
- end
117
-
118
- # Convert JSON to Object
119
- #
120
- # @param [String] json JSON to convert
121
- #
122
- # @return [Shale::Mapper]
123
- #
124
- # @api public
125
- def from_json(json)
126
- out_of_json(Shale.json_adapter.load(json))
127
- end
128
-
129
- # Convert Object to JSON document
130
- #
131
- # @param [Shale::Type::Base] instance Object to convert
132
- #
133
- # @return [Hash]
134
- #
135
- # @api public
136
- def as_json(instance)
137
- hash = {}
138
-
139
- instance.class.json_mapping.keys.each do |key, attr|
140
- attribute = instance.class.attributes[attr]
141
- next unless attribute
142
-
143
- value = instance.public_send(attribute.name)
144
-
145
- if value.nil?
146
- hash[key] = nil
147
- next
148
- end
149
-
150
- if attribute.collection?
151
- hash[key] = [*value].map { |v| v ? attribute.type.as_json(v) : v }
152
- else
153
- hash[key] = attribute.type.as_json(value)
154
- end
155
- end
156
-
157
- hash
158
- end
159
-
160
- # Convert Object to JSON
161
- #
162
- # @param [Shale::Type::Base] instance Object to convert
163
- #
164
- # @return [String]
165
- #
166
- # @api public
167
- def to_json(instance)
168
- Shale.json_adapter.dump(as_json(instance))
169
- end
170
-
171
- # Convert YAML document to Object
172
- #
173
- # @param [Hash] hash YAML document to convert
174
- #
175
- # @return [Shale::Mapper]
176
- #
177
- # @api public
178
- def out_of_yaml(hash)
179
- instance = new
180
-
181
- hash.each do |key, value|
182
- mapping = yaml_mapping.keys[key]
183
- next unless mapping
184
-
185
- attribute = attributes[mapping]
186
- next unless attribute
187
-
188
- if value.nil?
189
- instance.public_send("#{attribute.name}=", nil)
190
- next
191
- end
192
-
193
- if attribute.collection?
194
- [*value].each do |val|
195
- val = val ? attribute.type.out_of_yaml(val) : val
196
- instance.public_send(attribute.name) << attribute.type.cast(val)
197
- end
198
- else
199
- instance.public_send("#{attribute.name}=", attribute.type.out_of_yaml(value))
200
- end
201
- end
202
-
203
- instance
204
- end
205
-
206
- # Convert YAML to Object
207
- #
208
- # @param [String] yaml YAML to convert
209
- #
210
- # @return [Shale::Mapper]
211
- #
212
- # @api public
213
- def from_yaml(yaml)
214
- out_of_yaml(Shale.yaml_adapter.load(yaml))
215
- end
216
-
217
- # Convert Object to YAML document
218
- #
219
- # @param [Shale::Type::Base] instance Object to convert
220
- #
221
- # @return [Hash]
222
- #
223
- # @api public
224
- def as_yaml(instance)
225
- hash = {}
226
-
227
- instance.class.yaml_mapping.keys.each do |key, attr|
228
- attribute = instance.class.attributes[attr]
229
- next unless attribute
230
-
231
- value = instance.public_send(attribute.name)
232
-
233
- if value.nil?
234
- hash[key] = nil
235
- next
236
- end
237
-
238
- if attribute.collection?
239
- hash[key] = [*value].map { |v| v ? attribute.type.as_yaml(v) : v }
240
- else
241
- hash[key] = attribute.type.as_yaml(value)
242
- end
243
- end
244
-
245
- hash
246
- end
247
-
248
- # Convert Object to YAML
249
- #
250
- # @param [Shale::Type::Base] instance Object to convert
251
- #
252
- # @return [String]
253
- #
254
- # @api public
255
- def to_yaml(instance)
256
- Shale.yaml_adapter.dump(as_yaml(instance))
257
- end
258
-
259
- # Convert XML document to Object
260
- #
261
- # @param [Shale::Adapter::<XML adapter>::Node] xml XML to convert
262
- #
263
- # @return [Shale::Mapper]
264
- #
265
- # @api public
266
- def out_of_xml(element)
267
- instance = new
268
-
269
- element.attributes.each do |key, value|
270
- mapping = xml_mapping.attributes[key.to_s]
271
- next unless mapping
272
-
273
- attribute = attributes[mapping]
274
- next unless attribute
275
-
276
- if attribute.collection?
277
- instance.public_send(attribute.name) << attribute.type.cast(value)
278
- else
279
- instance.public_send("#{attribute.name}=", value)
280
- end
281
- end
282
-
283
- if xml_mapping.content
284
- attribute = attributes[xml_mapping.content]
285
-
286
- if attribute
287
- instance.public_send("#{attribute.name}=", attribute.type.out_of_xml(element))
288
- end
289
- end
290
-
291
- element.children.each do |node|
292
- mapping = xml_mapping.elements[node.name]
293
- next unless mapping
294
-
295
- attribute = attributes[mapping]
296
- next unless attribute
297
-
298
- if attribute.collection?
299
- value = attribute.type.out_of_xml(node)
300
- instance.public_send(attribute.name) << attribute.type.cast(value)
301
- else
302
- instance.public_send("#{attribute.name}=", attribute.type.out_of_xml(node))
303
- end
304
- end
305
-
306
- instance
307
- end
308
-
309
- # Convert XML to Object
310
- #
311
- # @param [String] xml XML to convert
312
- #
313
- # @return [Shale::Mapper]
314
- #
315
- # @api public
316
- def from_xml(xml)
317
- out_of_xml(Shale.xml_adapter.load(xml))
318
- end
319
-
320
- # Convert Object to XML document
321
- #
322
- # @param [Shale::Type::Base] instance Object to convert
323
- # @param [String, nil] node_name XML node name
324
- # @param [Shale::Adapter::<xml adapter>::Document, nil] doc Object to convert
325
- #
326
- # @return [::REXML::Document, ::Nokogiri::Document, ::Ox::Document]
327
- #
328
- # @api public
329
- def as_xml(instance, node_name = nil, doc = nil)
330
- unless doc
331
- doc = Shale.xml_adapter.create_document
332
- doc.add_element(doc.doc, as_xml(instance, instance.class.xml_mapping.root, doc))
333
- return doc.doc
334
- end
335
-
336
- element = doc.create_element(node_name)
337
-
338
- xml_mapping.attributes.each do |xml_attr, obj_attr|
339
- attribute = instance.class.attributes[obj_attr]
340
- next unless attribute
341
-
342
- value = instance.public_send(attribute.name)
343
-
344
- if value && !value.empty?
345
- doc.add_attribute(element, xml_attr, value)
346
- end
347
- end
348
-
349
- if xml_mapping.content
350
- attribute = instance.class.attributes[xml_mapping.content]
351
-
352
- if attribute
353
- value = instance.public_send(attribute.name)
354
- doc.add_text(element, value.to_s) if value
355
- end
356
- end
357
-
358
- xml_mapping.elements.each do |xml_name, obj_attr|
359
- attribute = instance.class.attributes[obj_attr]
360
- next unless attribute
361
-
362
- value = instance.public_send(attribute.name)
363
- next if value.nil?
364
-
365
- if attribute.collection?
366
- [*value].each do |v|
367
- next if v.nil?
368
- doc.add_element(element, attribute.type.as_xml(v, xml_name, doc))
369
- end
370
- else
371
- doc.add_element(element, attribute.type.as_xml(value, xml_name, doc))
372
- end
373
- end
374
-
375
- element
376
- end
377
-
378
- # Convert Object to XML
379
- #
380
- # @param [Shale::Type::Base] instance Object to convert
381
- #
382
- # @return [String]
383
- #
384
- # @api public
385
- def to_xml(instance)
386
- Shale.xml_adapter.dump(as_xml(instance))
387
- end
388
- end
389
-
390
- # Convert Object to Hash
391
- #
392
- # @return [Hash]
393
- #
394
- # @api public
395
- def to_hash
396
- self.class.to_hash(self)
397
- end
398
-
399
- # Convert Object to JSON
400
- #
401
- # @return [String]
402
- #
403
- # @api public
404
- def to_json
405
- self.class.to_json(self)
406
- end
407
-
408
- # Convert Object to YAML
409
- #
410
- # @return [String]
411
- #
412
- # @api public
413
- def to_yaml
414
- self.class.to_yaml(self)
415
- end
416
-
417
- # Convert Object to XML
418
- #
419
- # @return [String]
420
- #
421
- # @api public
422
- def to_xml
423
- self.class.to_xml(self)
424
- end
425
- end
426
- end
427
- end