shale 0.4.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +449 -40
  5. data/exe/shaleb +30 -6
  6. data/lib/shale/adapter/json.rb +3 -3
  7. data/lib/shale/adapter/nokogiri/document.rb +97 -0
  8. data/lib/shale/adapter/nokogiri/node.rb +100 -0
  9. data/lib/shale/adapter/nokogiri.rb +17 -156
  10. data/lib/shale/adapter/ox/document.rb +90 -0
  11. data/lib/shale/adapter/ox/node.rb +97 -0
  12. data/lib/shale/adapter/ox.rb +14 -138
  13. data/lib/shale/adapter/rexml/document.rb +98 -0
  14. data/lib/shale/adapter/rexml/node.rb +99 -0
  15. data/lib/shale/adapter/rexml.rb +14 -154
  16. data/lib/shale/adapter/toml_rb.rb +34 -0
  17. data/lib/shale/error.rb +57 -2
  18. data/lib/shale/mapper.rb +61 -9
  19. data/lib/shale/mapping/descriptor/dict.rb +12 -1
  20. data/lib/shale/mapping/descriptor/xml.rb +12 -2
  21. data/lib/shale/mapping/dict.rb +26 -2
  22. data/lib/shale/mapping/xml.rb +52 -6
  23. data/lib/shale/schema/{json_compiler → compiler}/boolean.rb +1 -1
  24. data/lib/shale/schema/{json_compiler/object.rb → compiler/complex.rb} +11 -8
  25. data/lib/shale/schema/{json_compiler → compiler}/date.rb +1 -1
  26. data/lib/shale/schema/{json_compiler → compiler}/float.rb +1 -1
  27. data/lib/shale/schema/{json_compiler → compiler}/integer.rb +1 -1
  28. data/lib/shale/schema/{json_compiler → compiler}/property.rb +6 -6
  29. data/lib/shale/schema/{json_compiler → compiler}/string.rb +1 -1
  30. data/lib/shale/schema/{json_compiler → compiler}/time.rb +1 -1
  31. data/lib/shale/schema/compiler/value.rb +21 -0
  32. data/lib/shale/schema/compiler/xml_complex.rb +50 -0
  33. data/lib/shale/schema/compiler/xml_property.rb +73 -0
  34. data/lib/shale/schema/json_compiler.rb +32 -34
  35. data/lib/shale/schema/json_generator.rb +4 -6
  36. data/lib/shale/schema/xml_compiler.rb +919 -0
  37. data/lib/shale/schema/xml_generator/import.rb +2 -2
  38. data/lib/shale/schema/xml_generator.rb +11 -13
  39. data/lib/shale/schema.rb +16 -0
  40. data/lib/shale/type/complex.rb +763 -0
  41. data/lib/shale/type/value.rb +38 -9
  42. data/lib/shale/utils.rb +42 -7
  43. data/lib/shale/version.rb +1 -1
  44. data/lib/shale.rb +22 -19
  45. data/shale.gemspec +3 -3
  46. metadata +26 -17
  47. data/lib/shale/schema/json_compiler/utils.rb +0 -52
  48. data/lib/shale/schema/json_compiler/value.rb +0 -13
  49. data/lib/shale/type/composite.rb +0 -331
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 26877841702bfce542b42206f519ffd1b03e2f0d3db1e70a42e37836ff0f3bff
4
- data.tar.gz: 23fddba7256bc38b5d64309cf56833f47fd636ed0e9f6ec61f2a076764dad2bb
3
+ metadata.gz: 5f3c860cf358f78476f8f22a86006694c3d9fd6cb554c945d7364bbf8bb70815
4
+ data.tar.gz: 5eba158f49fdcfb3012f2e4dcf804fdc4c8fe1e0c37e8e3dbb37972aa469c8bb
5
5
  SHA512:
6
- metadata.gz: e98bd0bbb20fbbaf4a8bdbfb5e2f83ba5d7bae78ad66a8597a1fe28c0c13d9799bc3ade785105ea7261147f55590ff530e85c31da459fe8c9171ee5b68f02977
7
- data.tar.gz: 54be0efe36f4d330c551f1bd8994a36b360ecec3696aafe12382408a0656e50a181b91ff54ec9f39758908d15fcf51eafc4810540cd1aeb00bda7b552382bc80
6
+ metadata.gz: b002a6837137fa024788e50b2b408ebd7d80f1fd73c7bf1182a2d0b0ab784a07cbda08147af703c4ded76efcb086958049c3d82573e5f885d1b1745f9312eb0c
7
+ data.tar.gz: e66ba2657a540ad6e9cebd98cf4a61a9ae4b31c0fc6ba1e8085493cc727eda48e2cb87515d399a2524cac8bb4d92932f57dd548afa2ba1d8fb5b97bb5c8ef1a7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,37 @@
1
+ ## [0.7.0] - [2022-08-09]
2
+
3
+ ### Added
4
+ - `only: []` and `except: []` options that allow to controll what attributes are rendered/parsed
5
+ - `render_nil: true` option that allows to render nil values
6
+ - Allow to pass a context object to extractor/generator methods
7
+
8
+ ### Changed
9
+ - Pass whole document to methods for JSON/YAML/TOML so its behavior is consistent with XML mapping
10
+ - Convert splat arguments to keyword arguments
11
+ - RSpec: enable random spec execution and warnings
12
+
13
+ ## [0.6.0] - 2022-07-05
14
+
15
+ ### Added
16
+ - Support for TOML
17
+ - Support for CDATA nodes in XML documents
18
+ - Support for using custom models
19
+
20
+ ### Fixed
21
+ - Allow to map XML content using methods
22
+ - Prevent adding default mapping after mapping block was declared
23
+
24
+ ## [0.5.0] - 2022-06-28
25
+
26
+ ### Added
27
+ - Allow to generate Shale model from XML Schema
28
+
29
+ ### Changed
30
+ - Shale doesn't defaults to REXML anymore - XML adapter needs to be set explicitly
31
+ - Rename "JSONSchemaError" to "SchemaError"
32
+ - Rename "Composite" type to "Complex"
33
+ - Drop support for Ruby 2.6
34
+
1
35
  ## [0.4.0] - 2022-05-30
2
36
 
3
37
  ### Added
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2021 TODO: Write your name
3
+ Copyright (c) 2021 Kamil Giszczak
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,21 +1,23 @@
1
1
  # Shale
2
2
 
3
- Shale is a Ruby object mapper and serializer for JSON, YAML and XML.
4
- It allows you to parse JSON, YAML and XML data and convert it into Ruby data structures,
5
- as well as serialize data structures into JSON, YAML or XML.
3
+ Shale is a Ruby object mapper and serializer for JSON, YAML, TOML and XML.
4
+ It allows you to parse JSON, YAML, TOML and XML data and convert it into Ruby data structures,
5
+ as well as serialize data structures into JSON, YAML, TOML or XML.
6
+
7
+ Documentation with interactive examples is available at [Shale website](https://www.shalerb.org)
6
8
 
7
9
  ## Features
8
10
 
9
- * Convert JSON, YAML and XML to Ruby data model
10
- * Convert Ruby data model to JSON, YAML and XML
11
+ * Convert JSON, YAML, TOML and XML to Ruby data model
12
+ * Convert Ruby data model to JSON, YAML, TOML and XML
11
13
  * Generate JSON and XML Schema from Ruby models
12
- * Compile JSON Schema into Ruby models
13
- * Out of the box support for JSON, YAML, Nokogiri, REXML and Ox parsers
14
+ * Compile JSON and XML Schema into Ruby models
15
+ * Out of the box support for JSON, YAML, Tomlib, toml-rb, Nokogiri, REXML and Ox parsers
14
16
  * Support for custom adapters
15
17
 
16
18
  ## Installation
17
19
 
18
- Shale supports Ruby (MRI) 2.6+
20
+ Shale supports Ruby (MRI) 2.7+
19
21
 
20
22
  Add this line to your application's Gemfile:
21
23
 
@@ -43,28 +45,32 @@ $ gem install shale
43
45
  * [Converting object to JSON](#converting-object-to-json)
44
46
  * [Converting YAML to object](#converting-yaml-to-object)
45
47
  * [Converting object to YAML](#converting-object-to-yaml)
48
+ * [Converting TOML to object](#converting-toml-to-object)
49
+ * [Converting object to TOML](#converting-object-to-toml)
46
50
  * [Converting Hash to object](#converting-hash-to-object)
47
51
  * [Converting object to Hash](#converting-object-to-hash)
48
52
  * [Converting XML to object](#converting-xml-to-object)
49
53
  * [Converting object to XML](#converting-object-to-xml)
50
54
  * [Mapping JSON keys to object attributes](#mapping-json-keys-to-object-attributes)
51
55
  * [Mapping YAML keys to object attributes](#mapping-yaml-keys-to-object-attributes)
56
+ * [Mapping TOML keys to object attributes](#mapping-toml-keys-to-object-attributes)
52
57
  * [Mapping Hash keys to object attributes](#mapping-hash-keys-to-object-attributes)
53
58
  * [Mapping XML elements and attributes to object attributes](#mapping-xml-elements-and-attributes-to-object-attributes)
54
59
  * [Using XML namespaces](#using-xml-namespaces)
60
+ * [Rendering nil values](#rendering-nil-values)
55
61
  * [Using methods to extract and generate data](#using-methods-to-extract-and-generate-data)
56
- * [Pretty printing and XML declaration](#pretty-printing-and-xml-declaration)
62
+ * [Additional options](#additional-options)
63
+ * [Using custom models](#using-custom-models)
57
64
  * [Supported types](#supported-types)
58
65
  * [Writing your own type](#writing-your-own-type)
59
66
  * [Adapters](#adapters)
60
67
  * [Generating JSON Schema](#generating-json-schema)
61
68
  * [Compiling JSON Schema into Shale model](#compiling-json-schema-into-shale-model)
62
69
  * [Generating XML Schema](#generating-xml-schema)
70
+ * [Compiling XML Schema into Shale model](#compiling-xml-schema-into-shale-model)
63
71
 
64
72
  ## Usage
65
73
 
66
- Documentation with interactive examples is available at [Shale website](https://www.shalerb.org)
67
-
68
74
  ### Simple use case
69
75
 
70
76
  ```ruby
@@ -194,6 +200,65 @@ person.to_yaml
194
200
  # zip: E1 6AN
195
201
  ```
196
202
 
203
+ ### Converting TOML to object
204
+
205
+ To use TOML with Shale you have to set adapter you want to use.
206
+ Out of the box Shale suports [Tomlib](https://github.com/kgiszczak/tomlib).
207
+ It also comes with adapter for [toml-rb](https://github.com/emancu/toml-rb) if you prefer that.
208
+ For details see [Adapters](#adapters) section.
209
+
210
+ To set it, first make sure Tomlib gem is installed:
211
+
212
+ ```
213
+ $ gem install tomlib
214
+ ```
215
+
216
+ then setup adapter:
217
+
218
+ ```ruby
219
+ require 'tomlib'
220
+ Shale.toml_adapter = Tomlib
221
+
222
+ # Alternatively if you'd like to use toml-rb, use:
223
+ require 'shale/adapter/toml_rb'
224
+ Shale.toml_adapter = Shale::Adapter::TomlRB
225
+ ```
226
+
227
+ Now you can use TOML with Shale:
228
+
229
+ ```ruby
230
+ person = Person.from_toml(<<~DATA)
231
+ first_name = "John"
232
+ last_name = "Doe"
233
+ age = 50
234
+ married = false
235
+ hobbies = ["Singing", "Dancing"]
236
+ [address]
237
+ city = "London"
238
+ street = "Oxford Street"
239
+ zip = "E1 6AN"
240
+ DATA
241
+ ```
242
+
243
+ ### Converting object to TOML
244
+
245
+ ```ruby
246
+ person.to_toml
247
+
248
+ # =>
249
+ #
250
+ # first_name = "John"
251
+ # last_name = "Doe"
252
+ # age = 50
253
+ # married = false
254
+ # hobbies = [ "Singing", "Dancing" ]
255
+ #
256
+ # [address]
257
+ # city = "London"
258
+ # street = "Oxford Street"
259
+ # zip = "E1 6AN"
260
+ ```
261
+
197
262
  ### Converting Hash to object
198
263
 
199
264
  ```ruby
@@ -230,6 +295,17 @@ person.to_hash
230
295
 
231
296
  ### Converting XML to object
232
297
 
298
+ To use XML with Shale you have to set adapter you want to use.
299
+ Shale comes with adapters for REXML, Nokogiri and OX parsers.
300
+ For details see [Adapters](#adapters) section.
301
+
302
+ ```ruby
303
+ require 'shale/adapter/rexml'
304
+ Shale.xml_adapter = Shale::Adapter::REXML
305
+ ```
306
+
307
+ Now you can use XML with Shale:
308
+
233
309
  ```ruby
234
310
  person = Person.from_xml(<<~DATA)
235
311
  <person>
@@ -274,6 +350,8 @@ person.to_xml
274
350
 
275
351
  By default keys are named the same as attributes. To use custom keys use:
276
352
 
353
+ :warning: **Declaring custom mapping removes default mapping for given format!**
354
+
277
355
  ```ruby
278
356
  class Person < Shale::Mapper
279
357
  attribute :first_name, Shale::Type::String
@@ -300,6 +378,20 @@ class Person < Shale::Mapper
300
378
  end
301
379
  ```
302
380
 
381
+ ### Mapping TOML keys to object attributes
382
+
383
+ ```ruby
384
+ class Person < Shale::Mapper
385
+ attribute :first_name, Shale::Type::String
386
+ attribute :last_name, Shale::Type::String
387
+
388
+ toml do
389
+ map 'firstName', to: :first_name
390
+ map 'lastName', to: :last_name
391
+ end
392
+ end
393
+ ```
394
+
303
395
  ### Mapping Hash keys to object attributes
304
396
 
305
397
  ```ruby
@@ -370,6 +462,37 @@ DATA
370
462
  - `map_attribute` - map element's attribute to attribute
371
463
  - `map_content` - map first text node to attribute
372
464
 
465
+ You can use `cdata: true` option on `map_element` and `map_content` to handle CDATA nodes:
466
+
467
+ ```ruby
468
+ class Address < Shale::Mapper
469
+ attribute :content, Shale::Type::String
470
+
471
+ xml do
472
+ map_content to: :content, cdata: true
473
+ end
474
+ end
475
+
476
+ class Person < Shale::Mapper
477
+ attribute :first_name, Shale::Type::String
478
+ attribute :address, Address
479
+
480
+ xml do
481
+ root 'Person'
482
+
483
+ map_element 'FirstName', to: :first_name, cdata: true
484
+ map_element 'Address', to: :address
485
+ end
486
+ end
487
+
488
+ person = Person.from_xml(<<~DATA)
489
+ <Person>
490
+ <FirstName><![CDATA[John]]></FirstName>
491
+ <Address><![CDATA[Oxford Street]]></Address>
492
+ </person>
493
+ DATA
494
+ ```
495
+
373
496
  ### Using XML namespaces
374
497
 
375
498
  To map namespaced elements and attributes use `namespace` and `prefix` properties on
@@ -436,6 +559,52 @@ person = Person.from_xml(<<~DATA)
436
559
  DATA
437
560
  ```
438
561
 
562
+ ### Rendering nil values
563
+
564
+ By default elements with `nil` value are not rendered. You can change this behavior
565
+ by using `render_nil: true` on a mapping.
566
+
567
+ ```ruby
568
+ class Person < Shale::Mapper
569
+ attribute :first_name, Shale::Type::String
570
+ attribute :last_name, Shale::Type::String
571
+ attribute :age, Shale::Type::Integer
572
+
573
+ json do
574
+ map 'first_name', to: :first_name, render_nil: true
575
+ map 'last_name', to: :last_name, render_nil: false
576
+ map 'age', to: :age, render_nil: true
577
+ end
578
+
579
+ xml do
580
+ root 'person'
581
+
582
+ map_element 'first_name', to: :first_name, render_nil: true
583
+ map_element 'last_name', to: :last_name, render_nil: false
584
+ map_attribute 'age', to: :age, render_nil: true
585
+ end
586
+ end
587
+
588
+ person = Person.new(first_name: nil, last_name: nil, age: nil)
589
+
590
+ puts person.to_json(pretty: true)
591
+
592
+ # =>
593
+ #
594
+ # {
595
+ # "first_name": null,
596
+ # "age": "null"
597
+ # }
598
+
599
+ puts person.to_xml(pretty: true)
600
+
601
+ # =>
602
+ #
603
+ # <person age="">
604
+ # <first_name/>
605
+ # </person>
606
+ ```
607
+
439
608
  ### Using methods to extract and generate data
440
609
 
441
610
  If you need full controll over extracting and generating data from/to document,
@@ -459,42 +628,42 @@ class Person < Shale::Mapper
459
628
  map_element 'Address', using: { from: :address_from_xml, to: :address_to_xml }
460
629
  end
461
630
 
462
- def hobbies_from_json(value)
463
- self.hobbies = value.split(',').map(&:strip)
631
+ def hobbies_from_json(model, value)
632
+ model.hobbies = value.split(',').map(&:strip)
464
633
  end
465
634
 
466
- def hobbies_to_json
467
- hobbies.join(', ')
635
+ def hobbies_to_json(model, doc)
636
+ doc['hobbies'] = model.hobbies.join(', ')
468
637
  end
469
638
 
470
- def address_from_json(value)
471
- self.street = value['street']
472
- self.city = value['city']
639
+ def address_from_json(model, value)
640
+ model.street = value['street']
641
+ model.city = value['city']
473
642
  end
474
643
 
475
- def address_to_json
476
- { 'street' => street, 'city' => city }
644
+ def address_to_json(model, doc)
645
+ doc['address'] = { 'street' => model.street, 'city' => model.city }
477
646
  end
478
647
 
479
- def hobbies_from_xml(value)
480
- self.hobbies = value.split(',').map(&:strip)
648
+ def hobbies_from_xml(model, value)
649
+ model.hobbies = value.split(',').map(&:strip)
481
650
  end
482
651
 
483
- def hobbies_to_xml(element, doc)
484
- doc.add_attribute(element, 'hobbies', hobbies.join(', '))
652
+ def hobbies_to_xml(model, element, doc)
653
+ doc.add_attribute(element, 'hobbies', model.hobbies.join(', '))
485
654
  end
486
655
 
487
- def address_from_xml(node)
488
- self.street = node.children.find { |e| e.name == 'Street' }.text
489
- self.city = node.children.find { |e| e.name == 'City' }.text
656
+ def address_from_xml(model, node)
657
+ model.street = node.children.find { |e| e.name == 'Street' }.text
658
+ model.city = node.children.find { |e| e.name == 'City' }.text
490
659
  end
491
660
 
492
- def address_to_xml(parent, doc)
661
+ def address_to_xml(model, parent, doc)
493
662
  street_element = doc.create_element('Street')
494
- doc.add_text(street_element, street.to_s)
663
+ doc.add_text(street_element, model.street.to_s)
495
664
 
496
665
  city_element = doc.create_element('City')
497
- doc.add_text(city_element, city.to_s)
666
+ doc.add_text(city_element, model.city.to_s)
498
667
 
499
668
  address_element = doc.create_element('Address')
500
669
  doc.add_element(address_element, street_element)
@@ -519,7 +688,7 @@ person = Person.from_xml(<<~DATA)
519
688
  <Street>Oxford Street</Street>
520
689
  <City>London</City>
521
690
  </Address>
522
- </person>
691
+ </Person>
523
692
  DATA
524
693
 
525
694
  # =>
@@ -530,12 +699,100 @@ DATA
530
699
  # @city="London">
531
700
  ```
532
701
 
533
- ### Pretty printing and XML declaration
702
+ You can also pass a `context` object that will be available in extractor/generator methods:
534
703
 
535
- If you need formatted output you can pass `:pretty` parameter to `#to_json` and `#to_xml`
704
+ ```ruby
705
+ class Person < Shale::Mapper
706
+ attribute :password, Shale::Type::String
707
+
708
+ json do
709
+ map 'password', using: { from: :password_from_json, to: :password_to_json }
710
+ end
711
+
712
+ def password_from_json(model, value, context)
713
+ if context.admin?
714
+ model.password = value
715
+ else
716
+ model.password = '*****'
717
+ end
718
+ end
719
+
720
+ def password_to_json(model, doc, context)
721
+ if context.admin?
722
+ doc['password'] = model.password
723
+ else
724
+ doc['password'] = '*****'
725
+ end
726
+ end
727
+ end
728
+
729
+ Person.new(password: 'secret').to_json(context: current_user)
730
+ ```
731
+
732
+ ### Additional options
733
+
734
+ You can control which attributes to render and parse by
735
+ using `only: []` and `except: []` parameters.
536
736
 
537
737
  ```ruby
538
- person.to_json(:pretty)
738
+ # e.g. if you have this model graph:
739
+ person = Person.new(
740
+ first_name: 'John'
741
+ last_name: 'Doe',
742
+ address: Address.new(city: 'London', street: 'Oxford Street')
743
+ )
744
+
745
+ # if you want to render only `first_name` and `address.city` do:
746
+ person.to_json(only: [:first_name, address: [:city]], pretty: true)
747
+
748
+ # =>
749
+ #
750
+ # {
751
+ # "first_name": "John",
752
+ # "address": {
753
+ # "city": "London"
754
+ # }
755
+ # }
756
+
757
+ # and if you don't need an address you can do:
758
+ person.to_json(except: [:address], pretty: true)
759
+
760
+ # =>
761
+ #
762
+ # {
763
+ # "first_name": "John",
764
+ # "last_name": "Doe"
765
+ # }
766
+ ```
767
+
768
+ It works the same for parsing:
769
+
770
+ ```ruby
771
+ # e.g. if you want to parse only `address.city` do:
772
+ Person.from_json(doc, only: [address: [:city]])
773
+
774
+ # =>
775
+ #
776
+ # #<Person:0x0000000113d7a488
777
+ # @first_name=nil,
778
+ # @last_name=nil,
779
+ # @address=#<Address:0x0000000113d7a140 @street=nil, @city="London">>
780
+
781
+ # and if you don't need an `address`:
782
+ Person.from_json(doc, except: [:address])
783
+
784
+ # =>
785
+ #
786
+ # #<Person:0x0000000113d7a488
787
+ # @first_name="John",
788
+ # @last_name="Doe",
789
+ # @address=nil>
790
+ ```
791
+
792
+ If you need formatted output you can pass `pretty: true` parameter to `#to_json` and `#to_xml`
793
+
794
+ ```ruby
795
+ person.to_json(pretty: true)
539
796
 
540
797
  # =>
541
798
  #
@@ -547,10 +804,10 @@ person.to_json(:pretty)
547
804
  # }
548
805
  ```
549
806
 
550
- You can also add an XML declaration by passing `:declaration` to `#to_xml`
807
+ You can also add an XML declaration by passing `declaration: true` to `#to_xml`
551
808
 
552
809
  ```ruby
553
- person.to_xml(:pretty, :declaration)
810
+ person.to_xml(pretty: true, declaration: true)
554
811
 
555
812
  # =>
556
813
  #
@@ -560,6 +817,67 @@ person.to_xml(:pretty, :declaration)
560
817
  # </Person>
561
818
  ```
562
819
 
820
+ ### Using custom models
821
+
822
+ By default Shale combines mapper and model into one class. If you want to use your own classes
823
+ as models you can do it by using `model` directive on the mapper:
824
+
825
+ ```ruby
826
+ class Address
827
+ attr_accessor :street, :city
828
+ end
829
+
830
+ class Person
831
+ attr_accessor :first_name, :last_name, :address
832
+ end
833
+
834
+ class AddressMapper < Shale::Mapper
835
+ model Address
836
+
837
+ attribute :street, Shale::Type::String
838
+ attribute :city, Shale::Type::String
839
+ end
840
+
841
+ class PersonMapper < Shale::Mapper
842
+ model Person
843
+
844
+ attribute :first_name, Shale::Type::String
845
+ attribute :last_name, Shale::Type::String
846
+ attribute :address, AddressMapper
847
+ end
848
+
849
+ person = PersonMapper.from_json(<<~DATA)
850
+ {
851
+ "first_name": "John",
852
+ "last_name": "Doe",
853
+ "address": {
854
+ "street": "Oxford Street",
855
+ "city": "London"
856
+ }
857
+ }
858
+ DATA
859
+
860
+ # =>
861
+ #
862
+ # #<Person:0x0000000113d7a488
863
+ # @first_name="John",
864
+ # @last_name="Doe",
865
+ # @address=#<Address:0x0000000113d7a140 @street="Oxford Street", @city="London">>
866
+
867
+ PersonMapper.to_json(person, pretty: true)
868
+
869
+ # =>
870
+ #
871
+ # {
872
+ # "first_name": "John",
873
+ # "last_name": "Doe",
874
+ # "address": {
875
+ # "street": "Oxford Street",
876
+ # "city": "London"
877
+ # }
878
+ # }
879
+ ```
880
+
563
881
  ### Supported types
564
882
 
565
883
  Shale supports these types out of the box:
@@ -588,10 +906,9 @@ end
588
906
  ### Adapters
589
907
 
590
908
  Shale uses adapters for parsing and generating documents.
591
- By default Ruby's standard JSON parser is used for handling JSON documents, YAML for YAML and
592
- REXML for XML.
909
+ By default Ruby's standard JSON, YAML parsers are used for handling JSON and YAML documents.
593
910
 
594
- You can change it by providing your own adapter. For JSON and YAML, adapter must implement
911
+ You can change it by providing your own adapter. For JSON, YAML and TOML, adapter must implement
595
912
  `.load` and `.dump` class methods.
596
913
 
597
914
  ```ruby
@@ -602,6 +919,22 @@ Shale.json_adapter = MultiJson
602
919
  Shale.yaml_adapter = MyYamlAdapter
603
920
  ```
604
921
 
922
+ To handle TOML documents you have to set TOML adapter. Out of the box `Tomlib` is supported.
923
+ Shale also provides adapter for `toml-rb` parser:
924
+
925
+ ```ruby
926
+ require 'shale'
927
+
928
+ # if you want to use Tomlib
929
+ require 'tomlib'
930
+ Shale.toml_adapter = Tomlib
931
+
932
+ # if you want to use toml-rb
933
+ require 'shale/adapter/toml_rb'
934
+ Shale.toml_adapter = Shale::Adapter::TomlRB
935
+ ```
936
+
937
+ To handle XML documents you have to explicitly set XML adapter.
605
938
  Shale provides adapters for most popular Ruby XML parsers:
606
939
 
607
940
  :warning: **Ox doesn't support XML namespaces**
@@ -609,7 +942,7 @@ Shale provides adapters for most popular Ruby XML parsers:
609
942
  ```ruby
610
943
  require 'shale'
611
944
 
612
- # REXML is used by default:
945
+ # if you want to use REXML:
613
946
 
614
947
  require 'shale/adapter/rexml'
615
948
  Shale.xml_adapter = Shale::Adapter::REXML
@@ -842,6 +1175,82 @@ end
842
1175
  Shale::Schema::XMLGenerator.register_xml_type(MyEmailType, 'myEmailXMLType')
843
1176
  ```
844
1177
 
1178
+ ### Compiling XML Schema into Shale model
1179
+
1180
+ To generate Shale data model from XML Schema use:
1181
+
1182
+ ```ruby
1183
+ require 'shale/schema'
1184
+
1185
+ schema = <<~SCHEMA
1186
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1187
+ <xs:element name="Person" type="Person" />
1188
+
1189
+ <xs:complexType name="Person">
1190
+ <xs:sequence>
1191
+ <xs:element name="FirstName" type="xs:string" />
1192
+ <xs:element name="LastName" type="xs:string" />
1193
+ <xs:element name="Address" type="Address" />
1194
+ </xs:sequence>
1195
+ </xs:complexType>
1196
+
1197
+ <xs:complexType name="Address">
1198
+ <xs:sequence>
1199
+ <xs:element name="Street" type="xs:string" />
1200
+ <xs:element name="City" type="xs:string" />
1201
+ </xs:sequence>
1202
+ </xs:complexType>
1203
+ </xs:schema>
1204
+ SCHEMA
1205
+
1206
+ Shale::Schema.from_xml([schema])
1207
+
1208
+ # =>
1209
+ #
1210
+ # {
1211
+ # "address" => "
1212
+ # require 'shale'
1213
+ #
1214
+ # class Address < Shale::Mapper
1215
+ # attribute :street, Shale::Type::String
1216
+ # attribute :city, Shale::Type::String
1217
+ #
1218
+ # xml do
1219
+ # root 'Address'
1220
+ #
1221
+ # map_element 'Street', to: :street
1222
+ # map_element 'City', to: :city
1223
+ # end
1224
+ # end
1225
+ # ",
1226
+ # "person" => "
1227
+ # require 'shale'
1228
+ #
1229
+ # require_relative 'address'
1230
+ #
1231
+ # class Person < Shale::Mapper
1232
+ # attribute :first_name, Shale::Type::String
1233
+ # attribute :last_name, Shale::Type::String
1234
+ # attribute :address, Address
1235
+ #
1236
+ # xml do
1237
+ # root 'Person'
1238
+ #
1239
+ # map_element 'FirstName', to: :first_name
1240
+ # map_element 'LastName', to: :last_name
1241
+ # map_element 'Address', to: :address
1242
+ # end
1243
+ # end
1244
+ # "
1245
+ # }
1246
+ ```
1247
+
1248
+ You can also use a command line tool to do it:
1249
+
1250
+ ```
1251
+ $ shaleb -c -f xml -i schema.xml
1252
+ ```
1253
+
845
1254
  ## Contributing
846
1255
 
847
1256
  Bug reports and pull requests are welcome on GitHub at https://github.com/kgiszczak/shale.