shale 0.8.0 → 1.0.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 +4 -4
- data/CHANGELOG.md +24 -0
- data/README.md +313 -62
- data/exe/shaleb +19 -4
- data/lib/shale/adapter/csv.rb +48 -0
- data/lib/shale/adapter/nokogiri/document.rb +7 -2
- data/lib/shale/adapter/nokogiri.rb +11 -4
- data/lib/shale/adapter/ox.rb +10 -4
- data/lib/shale/adapter/rexml.rb +18 -4
- data/lib/shale/error.rb +20 -8
- data/lib/shale/mapper.rb +41 -6
- data/lib/shale/mapping/delegates.rb +95 -0
- data/lib/shale/mapping/descriptor/dict.rb +10 -1
- data/lib/shale/mapping/descriptor/xml.rb +13 -2
- data/lib/shale/mapping/dict.rb +15 -5
- data/lib/shale/mapping/dict_base.rb +12 -6
- data/lib/shale/mapping/dict_group.rb +1 -1
- data/lib/shale/mapping/validator.rb +10 -3
- data/lib/shale/mapping/xml.rb +22 -6
- data/lib/shale/mapping/xml_base.rb +21 -12
- data/lib/shale/schema/compiler/complex.rb +52 -8
- data/lib/shale/schema/compiler/xml_complex.rb +5 -4
- data/lib/shale/schema/json_compiler.rb +27 -13
- data/lib/shale/schema/json_generator.rb +2 -2
- data/lib/shale/schema/xml_compiler.rb +46 -18
- data/lib/shale/schema/xml_generator.rb +3 -3
- data/lib/shale/schema.rb +10 -4
- data/lib/shale/type/complex.rb +291 -34
- data/lib/shale/type/date.rb +11 -0
- data/lib/shale/type/time.rb +11 -0
- data/lib/shale/type/value.rb +22 -0
- data/lib/shale/utils.rb +22 -4
- data/lib/shale/version.rb +1 -1
- data/lib/shale.rb +30 -4
- data/shale.gemspec +2 -2
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4e3b4d27ab7e8cc9b4873dfa60b52bdbafe50c2885085c4ec77ac6887351aab1
|
4
|
+
data.tar.gz: 6ce71a10ebf53633930b6d5a92ebb5f736aab44c47d6910a25238209192a4b92
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23c3b182c72c0e1d5102eb667541caccfb56e836e636bd083027425a60a12ed39ffce1ab3540adc0659c6b1bcbcf5255d192bbdc2d39bfc10c762ee4bdaaaa1e
|
7
|
+
data.tar.gz: ef7481e6076cc8f426c1dd0fb002d984594cbf3d59743a31450e8989048ffdd6cdc8fc735e90b7529c1935d7251c6751e70f7c3734d5c3f7df4b8124cc4b8256
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,27 @@
|
|
1
|
+
## [1.0.0] - 2023-07-15
|
2
|
+
|
3
|
+
### Added
|
4
|
+
- Support for Ruby 3.2
|
5
|
+
- Support for delegating fields to nested attributes
|
6
|
+
- JSON and XML schema namespace mapping support
|
7
|
+
- Allow to set render_nil defaults
|
8
|
+
|
9
|
+
### Changed
|
10
|
+
- Use `ShaleError` as a base class for exceptions
|
11
|
+
- Use model instead of mapper names for schema types
|
12
|
+
|
13
|
+
### Fixed
|
14
|
+
- Fix compilation error for bundled JSON schemas
|
15
|
+
- Fix type inference for XML schema when default namespace is used
|
16
|
+
- Fix XML schema handling with a period in the element name
|
17
|
+
|
18
|
+
## [0.9.0] - 2022-10-31
|
19
|
+
|
20
|
+
### Added
|
21
|
+
- Support for CSV
|
22
|
+
- Allow to specify version and add encoding to XML declaration
|
23
|
+
- Support for mapping/generating collections
|
24
|
+
|
1
25
|
## [0.8.0] - 2022-08-30
|
2
26
|
|
3
27
|
### Added
|
data/README.md
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
# Shale
|
2
2
|
|
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.
|
3
|
+
Shale is a Ruby object mapper and serializer for JSON, YAML, TOML, CSV and XML.
|
4
|
+
It allows you to parse JSON, YAML, TOML, CSV and XML data and convert it into Ruby data structures,
|
5
|
+
as well as serialize data structures into JSON, YAML, TOML, CSV or XML.
|
6
6
|
|
7
7
|
Documentation with interactive examples is available at [Shale website](https://www.shalerb.org)
|
8
8
|
|
9
9
|
## Features
|
10
10
|
|
11
|
-
* Convert JSON, YAML, TOML and XML to Ruby data model
|
12
|
-
* Convert Ruby data model to JSON, YAML, TOML and XML
|
11
|
+
* Convert JSON, YAML, TOML, CSV and XML to Ruby data model
|
12
|
+
* Convert Ruby data model to JSON, YAML, TOML, CSV and XML
|
13
13
|
* Generate JSON and XML Schema from Ruby models
|
14
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
|
15
|
+
* Out of the box support for JSON, YAML, Tomlib, toml-rb, CSV, Nokogiri, REXML and Ox parsers
|
16
16
|
* Support for custom adapters
|
17
17
|
|
18
18
|
## Installation
|
@@ -51,14 +51,19 @@ $ gem install shale
|
|
51
51
|
* [Converting object to Hash](#converting-object-to-hash)
|
52
52
|
* [Converting XML to object](#converting-xml-to-object)
|
53
53
|
* [Converting object to XML](#converting-object-to-xml)
|
54
|
+
* [Converting CSV to object](#converting-csv-to-object)
|
55
|
+
* [Converting object to CSV](#converting-object-to-csv)
|
56
|
+
* [Converting collections](#converting-collections)
|
54
57
|
* [Mapping JSON keys to object attributes](#mapping-json-keys-to-object-attributes)
|
55
58
|
* [Mapping YAML keys to object attributes](#mapping-yaml-keys-to-object-attributes)
|
56
59
|
* [Mapping TOML keys to object attributes](#mapping-toml-keys-to-object-attributes)
|
60
|
+
* [Mapping CSV columns to object attributes](#mapping-csv-columns-to-object-attributes)
|
57
61
|
* [Mapping Hash keys to object attributes](#mapping-hash-keys-to-object-attributes)
|
58
62
|
* [Mapping XML elements and attributes to object attributes](#mapping-xml-elements-and-attributes-to-object-attributes)
|
59
63
|
* [Using XML namespaces](#using-xml-namespaces)
|
60
64
|
* [Rendering nil values](#rendering-nil-values)
|
61
65
|
* [Using methods to extract and generate data](#using-methods-to-extract-and-generate-data)
|
66
|
+
* [Delegating fields to child attributes](#delegating-fields-to-child-attributes)
|
62
67
|
* [Additional options](#additional-options)
|
63
68
|
* [Using custom models](#using-custom-models)
|
64
69
|
* [Supported types](#supported-types)
|
@@ -346,6 +351,70 @@ person.to_xml
|
|
346
351
|
# </person>
|
347
352
|
```
|
348
353
|
|
354
|
+
### Converting CSV to object
|
355
|
+
|
356
|
+
CSV represents a flat data structure, so you can't map properties to complex types directly,
|
357
|
+
but you can use methods to map properties to complex types
|
358
|
+
(see [Using methods to extract and generate data](#using-methods-to-extract-and-generate-data)
|
359
|
+
section).
|
360
|
+
|
361
|
+
`.from_csv` method allways returns an array of records.
|
362
|
+
|
363
|
+
```ruby
|
364
|
+
people = Person.from_csv(<<~DATA)
|
365
|
+
John,Doe,50,false
|
366
|
+
DATA
|
367
|
+
```
|
368
|
+
|
369
|
+
### Converting object to CSV
|
370
|
+
|
371
|
+
```ruby
|
372
|
+
people[0].to_csv # or Person.to_csv(people) if you want to convert a collection
|
373
|
+
|
374
|
+
# =>
|
375
|
+
#
|
376
|
+
# John,Doe,50,false
|
377
|
+
```
|
378
|
+
|
379
|
+
### Converting collections
|
380
|
+
|
381
|
+
Shale allows converting collections for formats that support it (JSON, YAML and CSV).
|
382
|
+
To convert Ruby array to JSON:
|
383
|
+
|
384
|
+
```ruby
|
385
|
+
person1 = Person.new(name: 'John Doe')
|
386
|
+
person2 = Person.new(name: 'Joe Sixpack')
|
387
|
+
|
388
|
+
Person.to_json([person1, person2], pretty: true)
|
389
|
+
# or Person.to_yaml([person1, person2])
|
390
|
+
# or Person.to_csv([person1, person2])
|
391
|
+
|
392
|
+
# =>
|
393
|
+
#
|
394
|
+
# [
|
395
|
+
# { "name": "John Doe" },
|
396
|
+
# { "name": "Joe Sixpack" }
|
397
|
+
# ]
|
398
|
+
```
|
399
|
+
|
400
|
+
To convert JSON array to Ruby:
|
401
|
+
|
402
|
+
```ruby
|
403
|
+
Person.from_json(<<~JSON)
|
404
|
+
[
|
405
|
+
{ "name": "John Doe" },
|
406
|
+
{ "name": "Joe Sixpack" }
|
407
|
+
]
|
408
|
+
JSON
|
409
|
+
|
410
|
+
# =>
|
411
|
+
#
|
412
|
+
# [
|
413
|
+
# #<Person:0x00000001033dbce8 @name="John Doe">,
|
414
|
+
# #<Person:0x00000001033db4c8 @name="Joe Sixpack">
|
415
|
+
# ]
|
416
|
+
```
|
417
|
+
|
349
418
|
### Mapping JSON keys to object attributes
|
350
419
|
|
351
420
|
By default keys are named the same as attributes. To use custom keys use:
|
@@ -392,6 +461,24 @@ class Person < Shale::Mapper
|
|
392
461
|
end
|
393
462
|
```
|
394
463
|
|
464
|
+
### Mapping CSV columns to object attributes
|
465
|
+
|
466
|
+
For CSV the order of mapping matters, the first argument in the `map` method is only
|
467
|
+
used as a label in header row. So, in the example below the first column will be mapped
|
468
|
+
to `:first_name` attribute and the second column to `:last_name`.
|
469
|
+
|
470
|
+
```ruby
|
471
|
+
class Person < Shale::Mapper
|
472
|
+
attribute :first_name, Shale::Type::String
|
473
|
+
attribute :last_name, Shale::Type::String
|
474
|
+
|
475
|
+
csv do
|
476
|
+
map 'firstName', to: :first_name
|
477
|
+
map 'lastName', to: :last_name
|
478
|
+
end
|
479
|
+
end
|
480
|
+
```
|
481
|
+
|
395
482
|
### Mapping Hash keys to object attributes
|
396
483
|
|
397
484
|
```ruby
|
@@ -561,8 +648,9 @@ DATA
|
|
561
648
|
|
562
649
|
### Rendering nil values
|
563
650
|
|
564
|
-
|
565
|
-
by using `render_nil: true` on a mapping.
|
651
|
+
For JSON, YAML, TOML and XML by default, elements with `nil` value are not rendered.
|
652
|
+
You can change this behavior by using `render_nil: true` on a mapping.
|
653
|
+
For CSV the default is to render `nil` elements.
|
566
654
|
|
567
655
|
```ruby
|
568
656
|
class Person < Shale::Mapper
|
@@ -605,6 +693,49 @@ puts person.to_xml(pretty: true)
|
|
605
693
|
# </person>
|
606
694
|
```
|
607
695
|
|
696
|
+
If you want to change how nil values are rendered for all mappings you can use `render_nil` method:
|
697
|
+
|
698
|
+
```ruby
|
699
|
+
class Base < Shale::Mapper
|
700
|
+
json do
|
701
|
+
# change render_nil default for all JSON mappings inheriting from Base class
|
702
|
+
render_nil true
|
703
|
+
end
|
704
|
+
end
|
705
|
+
|
706
|
+
class Person < Base
|
707
|
+
attribute :first_name, Shale::Type::String
|
708
|
+
attribute :last_name, Shale::Type::String
|
709
|
+
attribute :age, Shale::Type::Integer
|
710
|
+
|
711
|
+
json do
|
712
|
+
# override default from Base class
|
713
|
+
render_nil false
|
714
|
+
|
715
|
+
map 'first_name', to: :first_name
|
716
|
+
map 'last_name', to: :last_name
|
717
|
+
map 'age', to: :age, render_nil: true # override default
|
718
|
+
end
|
719
|
+
end
|
720
|
+
```
|
721
|
+
|
722
|
+
:warning: The default affects only the mappings declared after setting the default value e.g.
|
723
|
+
|
724
|
+
```ruby
|
725
|
+
class Person < Base
|
726
|
+
attribute :first_name, Shale::Type::String
|
727
|
+
attribute :last_name, Shale::Type::String
|
728
|
+
|
729
|
+
json do
|
730
|
+
render_nil false
|
731
|
+
map 'first_name', to: :first_name # render_nil will be false for this mapping
|
732
|
+
|
733
|
+
render_nil true
|
734
|
+
map 'last_name', to: :last_name # render_nil will be true for this mapping
|
735
|
+
end
|
736
|
+
end
|
737
|
+
```
|
738
|
+
|
608
739
|
### Using methods to extract and generate data
|
609
740
|
|
610
741
|
If you need full controll over extracting and generating data from/to document,
|
@@ -778,6 +909,42 @@ DATA
|
|
778
909
|
# => #<Person:0x00007f9bc3086d60 @name="John Doe">
|
779
910
|
```
|
780
911
|
|
912
|
+
### Delegating fields to child attributes
|
913
|
+
|
914
|
+
To delegate fields to child complex types you can use `receiver: :child` declaration:
|
915
|
+
|
916
|
+
```ruby
|
917
|
+
class Address < Shale::Mapper
|
918
|
+
attribute :city, Shale::Type::String
|
919
|
+
attribute :street, Shale::Type::String
|
920
|
+
end
|
921
|
+
|
922
|
+
class Person < Shale::Mapper
|
923
|
+
attribute :name, Shale::Type::String
|
924
|
+
attribute :address, Address
|
925
|
+
|
926
|
+
json do
|
927
|
+
map 'name', to: :name
|
928
|
+
map 'city', to: :city, receiver: :address
|
929
|
+
map 'street', to: :street, receiver: :address
|
930
|
+
end
|
931
|
+
end
|
932
|
+
|
933
|
+
person = Person.from_json(<<~DATA)
|
934
|
+
{
|
935
|
+
"name": "John Doe",
|
936
|
+
"city": "London",
|
937
|
+
"street": "Oxford Street"
|
938
|
+
}
|
939
|
+
DATA
|
940
|
+
|
941
|
+
# =>
|
942
|
+
#
|
943
|
+
# #<Person:0x00007f9bc3086d60
|
944
|
+
# @name="John Doe",
|
945
|
+
# @address=#<Address:0x0000000102cbd218 @city="London", @street="Oxford Street">>
|
946
|
+
```
|
947
|
+
|
781
948
|
### Additional options
|
782
949
|
|
783
950
|
You can control which attributes to render and parse by
|
@@ -853,19 +1020,52 @@ person.to_json(pretty: true)
|
|
853
1020
|
# }
|
854
1021
|
```
|
855
1022
|
|
856
|
-
You can also add an XML declaration by passing `declaration: true`
|
1023
|
+
You can also add an XML declaration by passing `declaration: true` and `encoding: true`
|
1024
|
+
or if you want to spcify version: `declaration: '1.1'` and `encoding: 'ASCII'` to `#to_xml`
|
857
1025
|
|
858
1026
|
```ruby
|
859
|
-
person.to_xml(pretty: true, declaration: true)
|
1027
|
+
person.to_xml(pretty: true, declaration: true, encoding: true)
|
860
1028
|
|
861
1029
|
# =>
|
862
1030
|
#
|
863
|
-
# <?xml version="1.0"?>
|
1031
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
864
1032
|
# <Person>
|
865
1033
|
# <Address city="London"/>
|
866
1034
|
# </Person>
|
867
1035
|
```
|
868
1036
|
|
1037
|
+
For CSV you can pass `headers: true` to indicate that the first row contains column
|
1038
|
+
names and shouldn't be included in the returned collection. It also accepts all the options that
|
1039
|
+
[CSV parser](https://ruby-doc.org/stdlib-3.1.2/libdoc/csv/rdoc/CSV.html#class-CSV-label-Options) accepts.
|
1040
|
+
|
1041
|
+
```ruby
|
1042
|
+
class Person
|
1043
|
+
attribute :first_name, Shale::Type::String
|
1044
|
+
attribute :last_name, Shale::Type::String
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
people = Person.from_csv(<<~DATA, headers: true, col_sep: '|')
|
1048
|
+
first_name|last_name
|
1049
|
+
John|Doe
|
1050
|
+
James|Sixpack
|
1051
|
+
DATA
|
1052
|
+
|
1053
|
+
# =>
|
1054
|
+
#
|
1055
|
+
# [
|
1056
|
+
# #<Person:0x0000000113d7a488 @first_name="John", @last_name="Doe">,
|
1057
|
+
# #<Person:0x0000000113d7a488 @first_name="James", @last_name="Sixpack">
|
1058
|
+
# ]
|
1059
|
+
|
1060
|
+
Person.to_csv(people, headers: true, col_sep: '|')
|
1061
|
+
|
1062
|
+
# =>
|
1063
|
+
#
|
1064
|
+
# first_name|last_name
|
1065
|
+
# John|Doe
|
1066
|
+
# James|Sixpack
|
1067
|
+
```
|
1068
|
+
|
869
1069
|
### Using custom models
|
870
1070
|
|
871
1071
|
By default Shale combines mapper and model into one class. If you want to use your own classes
|
@@ -955,10 +1155,10 @@ end
|
|
955
1155
|
### Adapters
|
956
1156
|
|
957
1157
|
Shale uses adapters for parsing and generating documents.
|
958
|
-
By default Ruby's standard JSON, YAML parsers are used for handling JSON
|
1158
|
+
By default Ruby's standard JSON, YAML, CSV parsers are used for handling JSON YAML, CSV documents.
|
959
1159
|
|
960
|
-
You can change it by providing your own adapter. For JSON, YAML and
|
961
|
-
`.load` and `.dump` class methods.
|
1160
|
+
You can change it by providing your own adapter. For JSON, YAML, TOML and CSV adapter must
|
1161
|
+
implement `.load` and `.dump` class methods.
|
962
1162
|
|
963
1163
|
```ruby
|
964
1164
|
require 'shale'
|
@@ -1092,7 +1292,9 @@ Shale::Schema::JSONGenerator.register_json_type(MyEmailType, MyEmailJSONType)
|
|
1092
1292
|
|
1093
1293
|
:warning: Only **[Draft 2020-12](https://json-schema.org/draft/2020-12/schema)** JSON Schema is supported
|
1094
1294
|
|
1095
|
-
To generate Shale data model from JSON Schema use
|
1295
|
+
To generate Shale data model from JSON Schema use `Shale::Schema.from_json`.
|
1296
|
+
You can pass `root_name: 'Foobar'` to change the name of the root type and
|
1297
|
+
`namespace_mapping: {}` to map schemas to Ruby modules:
|
1096
1298
|
|
1097
1299
|
```ruby
|
1098
1300
|
require 'shale/schema'
|
@@ -1103,7 +1305,11 @@ schema = <<~SCHEMA
|
|
1103
1305
|
"properties": {
|
1104
1306
|
"firstName": { "type": "string" },
|
1105
1307
|
"lastName": { "type": "string" },
|
1106
|
-
"address": {
|
1308
|
+
"address": { "$ref": "http://bar.com" }
|
1309
|
+
},
|
1310
|
+
"$defs": {
|
1311
|
+
"Address": {
|
1312
|
+
"$id": "http://bar.com",
|
1107
1313
|
"type": "object",
|
1108
1314
|
"properties": {
|
1109
1315
|
"street": { "type": "string" },
|
@@ -1114,38 +1320,53 @@ schema = <<~SCHEMA
|
|
1114
1320
|
}
|
1115
1321
|
SCHEMA
|
1116
1322
|
|
1117
|
-
Shale::Schema.from_json(
|
1323
|
+
Shale::Schema.from_json(
|
1324
|
+
[schema],
|
1325
|
+
root_name: 'Person',
|
1326
|
+
namespace_mapping: {
|
1327
|
+
nil => 'Api::Foo', # default schema (without ID)
|
1328
|
+
'http://bar.com' => 'Api::Bar',
|
1329
|
+
}
|
1330
|
+
)
|
1118
1331
|
|
1119
1332
|
# =>
|
1120
1333
|
#
|
1121
1334
|
# {
|
1122
|
-
# "address" => "
|
1335
|
+
# "api/bar/address" => "
|
1123
1336
|
# require 'shale'
|
1124
1337
|
#
|
1125
|
-
#
|
1126
|
-
#
|
1127
|
-
#
|
1338
|
+
# module Api
|
1339
|
+
# module Bar
|
1340
|
+
# class Address < Shale::Mapper
|
1341
|
+
# attribute :street, Shale::Type::String
|
1342
|
+
# attribute :city, Shale::Type::String
|
1128
1343
|
#
|
1129
|
-
#
|
1130
|
-
#
|
1131
|
-
#
|
1344
|
+
# json do
|
1345
|
+
# map 'street', to: :street
|
1346
|
+
# map 'city', to: :city
|
1347
|
+
# end
|
1348
|
+
# end
|
1132
1349
|
# end
|
1133
1350
|
# end
|
1134
1351
|
# ",
|
1135
|
-
# "person" => "
|
1352
|
+
# "api/foo/person" => "
|
1136
1353
|
# require 'shale'
|
1137
1354
|
#
|
1138
|
-
# require_relative 'address'
|
1355
|
+
# require_relative '../bar/address'
|
1139
1356
|
#
|
1140
|
-
#
|
1141
|
-
#
|
1142
|
-
#
|
1143
|
-
#
|
1357
|
+
# module Api
|
1358
|
+
# module Foo
|
1359
|
+
# class Person < Shale::Mapper
|
1360
|
+
# attribute :first_name, Shale::Type::String
|
1361
|
+
# attribute :last_name, Shale::Type::String
|
1362
|
+
# attribute :address, Api::Bar::Address
|
1144
1363
|
#
|
1145
|
-
#
|
1146
|
-
#
|
1147
|
-
#
|
1148
|
-
#
|
1364
|
+
# json do
|
1365
|
+
# map 'firstName', to: :first_name
|
1366
|
+
# map 'lastName', to: :last_name
|
1367
|
+
# map 'address', to: :address
|
1368
|
+
# end
|
1369
|
+
# end
|
1149
1370
|
# end
|
1150
1371
|
# end
|
1151
1372
|
# "
|
@@ -1155,7 +1376,7 @@ Shale::Schema.from_json([schema], root_name: 'Person')
|
|
1155
1376
|
You can also use a command line tool to do it:
|
1156
1377
|
|
1157
1378
|
```
|
1158
|
-
$ shaleb -c -i schema.json -r Person
|
1379
|
+
$ shaleb -c -i schema.json -r Person -m http://bar.com=Api::Bar,=Api::Foo
|
1159
1380
|
```
|
1160
1381
|
|
1161
1382
|
### Generating XML Schema
|
@@ -1226,22 +1447,39 @@ Shale::Schema::XMLGenerator.register_xml_type(MyEmailType, 'myEmailXMLType')
|
|
1226
1447
|
|
1227
1448
|
### Compiling XML Schema into Shale model
|
1228
1449
|
|
1229
|
-
To generate Shale data model from XML Schema use
|
1450
|
+
To generate Shale data model from XML Schema use `Shale::Schema.from_xml`.
|
1451
|
+
You can pass `namespace_mapping: {}` to map XML namespaces to Ruby modules:
|
1230
1452
|
|
1231
1453
|
```ruby
|
1232
1454
|
require 'shale/schema'
|
1233
1455
|
|
1234
|
-
|
1235
|
-
<xs:schema
|
1456
|
+
schema1 = <<~SCHEMA
|
1457
|
+
<xs:schema
|
1458
|
+
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
1459
|
+
xmlns:bar="http://bar.com"
|
1460
|
+
elementFormDefault="qualified"
|
1461
|
+
>
|
1462
|
+
<xs:import namespace="http://bar.com" />
|
1463
|
+
|
1236
1464
|
<xs:element name="Person" type="Person" />
|
1237
1465
|
|
1238
1466
|
<xs:complexType name="Person">
|
1239
1467
|
<xs:sequence>
|
1240
|
-
<xs:element name="
|
1241
|
-
<xs:element
|
1242
|
-
<xs:element name="Address" type="Address" />
|
1468
|
+
<xs:element name="Name" type="xs:string" />
|
1469
|
+
<xs:element ref="bar:Address" />
|
1243
1470
|
</xs:sequence>
|
1244
1471
|
</xs:complexType>
|
1472
|
+
</xs:schema>
|
1473
|
+
SCHEMA
|
1474
|
+
|
1475
|
+
schema2 = <<~SCHEMA
|
1476
|
+
<xs:schema
|
1477
|
+
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
1478
|
+
xmlns:bar="http://bar.com"
|
1479
|
+
targetNamespace="http://bar.com"
|
1480
|
+
elementFormDefault="qualified"
|
1481
|
+
>
|
1482
|
+
<xs:element name="Address" type="bar:Address" />
|
1245
1483
|
|
1246
1484
|
<xs:complexType name="Address">
|
1247
1485
|
<xs:sequence>
|
@@ -1252,42 +1490,55 @@ schema = <<~SCHEMA
|
|
1252
1490
|
</xs:schema>
|
1253
1491
|
SCHEMA
|
1254
1492
|
|
1255
|
-
Shale::Schema.from_xml(
|
1493
|
+
Shale::Schema.from_xml(
|
1494
|
+
[schema1, schema2],
|
1495
|
+
namespace_mapping: {
|
1496
|
+
nil => 'Api::Foo', # no namespace
|
1497
|
+
'http://bar.com' => 'Api::Bar',
|
1498
|
+
}
|
1499
|
+
)
|
1256
1500
|
|
1257
1501
|
# =>
|
1258
1502
|
#
|
1259
1503
|
# {
|
1260
|
-
# "address" => "
|
1504
|
+
# "api/bar/address" => "
|
1261
1505
|
# require 'shale'
|
1262
1506
|
#
|
1263
|
-
#
|
1264
|
-
#
|
1265
|
-
#
|
1507
|
+
# module Api
|
1508
|
+
# module Bar
|
1509
|
+
# class Address < Shale::Mapper
|
1510
|
+
# attribute :street, Shale::Type::String
|
1511
|
+
# attribute :city, Shale::Type::String
|
1266
1512
|
#
|
1267
|
-
#
|
1268
|
-
#
|
1513
|
+
# xml do
|
1514
|
+
# root 'Address'
|
1515
|
+
# namespace 'http://bar.com', 'bar'
|
1269
1516
|
#
|
1270
|
-
#
|
1271
|
-
#
|
1517
|
+
# map_element 'Street', to: :street
|
1518
|
+
# map_element 'City', to: :city
|
1519
|
+
# end
|
1520
|
+
# end
|
1272
1521
|
# end
|
1273
1522
|
# end
|
1274
1523
|
# ",
|
1275
|
-
# "person" => "
|
1524
|
+
# "api/foo/person" => "
|
1276
1525
|
# require 'shale'
|
1277
1526
|
#
|
1278
|
-
# require_relative 'address'
|
1527
|
+
# require_relative '../bar/address'
|
1279
1528
|
#
|
1280
|
-
#
|
1281
|
-
#
|
1282
|
-
#
|
1283
|
-
#
|
1529
|
+
# module Api
|
1530
|
+
# module Foo
|
1531
|
+
# class Person < Shale::Mapper
|
1532
|
+
# attribute :name, Shale::Type::String
|
1533
|
+
# attribute :address, Api::Bar::Address
|
1284
1534
|
#
|
1285
|
-
#
|
1286
|
-
#
|
1535
|
+
# xml do
|
1536
|
+
# root 'Person'
|
1287
1537
|
#
|
1288
|
-
#
|
1289
|
-
#
|
1290
|
-
#
|
1538
|
+
# map_element 'Name', to: :name
|
1539
|
+
# map_element 'Address', to: :address, prefix: 'bar', namespace: 'http://bar.com'
|
1540
|
+
# end
|
1541
|
+
# end
|
1291
1542
|
# end
|
1292
1543
|
# end
|
1293
1544
|
# "
|
@@ -1297,7 +1548,7 @@ Shale::Schema.from_xml([schema])
|
|
1297
1548
|
You can also use a command line tool to do it:
|
1298
1549
|
|
1299
1550
|
```
|
1300
|
-
$ shaleb -c -f xml -i schema.xml
|
1551
|
+
$ shaleb -c -f xml -i schema.xml -m http://bar.com=Api::Bar,=Api::Foo
|
1301
1552
|
```
|
1302
1553
|
|
1303
1554
|
## Contributing
|
data/exe/shaleb
CHANGED
@@ -36,8 +36,8 @@ ARGV << '-h' if ARGV.empty?
|
|
36
36
|
OptionParser.new do |opts|
|
37
37
|
opts.banner = <<~BANNER
|
38
38
|
Usage: shaleb [options]
|
39
|
-
example generate schema from Shale model: shaleb -g -i data_model.rb -
|
40
|
-
example generate Shale model from schema: shaleb -c -i schema1.json,schema2.json -
|
39
|
+
example generate schema from Shale model: shaleb -g -i data_model.rb -r MyRoot
|
40
|
+
example generate Shale model from schema: shaleb -c -i schema1.json,schema2.json -r MyRoot
|
41
41
|
BANNER
|
42
42
|
|
43
43
|
opts.on('-g', '--generate', 'generate schema from Shale model')
|
@@ -47,6 +47,7 @@ OptionParser.new do |opts|
|
|
47
47
|
opts.on('-r ROOT', '--root ROOT', 'Shale model class name')
|
48
48
|
opts.on('-f FORMAT', '--format FORMAT', 'Schema format: JSON (default), XML')
|
49
49
|
opts.on('-p', '--pretty', 'Pretty print generated schema')
|
50
|
+
opts.on('-m MAPPING', '--mapping', Array, 'Namespace mapping')
|
50
51
|
|
51
52
|
opts.on('-v', '--version', 'Show version') do
|
52
53
|
puts "shaleb version #{Shale::VERSION}"
|
@@ -71,11 +72,24 @@ if params[:compile]
|
|
71
72
|
end
|
72
73
|
end
|
73
74
|
|
75
|
+
if params[:mapping]
|
76
|
+
namespace_mapping = params[:mapping]
|
77
|
+
.to_h { |e| [*e.split('='), nil][0, 2] }
|
78
|
+
.transform_keys { |key| key.empty? ? nil : key }
|
79
|
+
end
|
80
|
+
|
74
81
|
if params[:format] == 'xml'
|
75
82
|
load_xml_parser
|
76
|
-
models = Shale::Schema.from_xml(
|
83
|
+
models = Shale::Schema.from_xml(
|
84
|
+
schemas,
|
85
|
+
namespace_mapping: namespace_mapping
|
86
|
+
)
|
77
87
|
else
|
78
|
-
models = Shale::Schema.from_json(
|
88
|
+
models = Shale::Schema.from_json(
|
89
|
+
schemas,
|
90
|
+
root_name: params[:root],
|
91
|
+
namespace_mapping: namespace_mapping
|
92
|
+
)
|
79
93
|
end
|
80
94
|
|
81
95
|
if params[:output]
|
@@ -84,6 +98,7 @@ if params[:compile]
|
|
84
98
|
|
85
99
|
models.each do |name, model|
|
86
100
|
output_path = File.join(dir, "#{name}.rb")
|
101
|
+
FileUtils.mkdir_p(File.dirname(output_path))
|
87
102
|
File.write(output_path, model)
|
88
103
|
end
|
89
104
|
else
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
|
5
|
+
module Shale
|
6
|
+
module Adapter
|
7
|
+
# CSV adapter
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
class CSV
|
11
|
+
# Parse CSV into Array<Hash<String, any>>
|
12
|
+
#
|
13
|
+
# @param [String] csv CSV document
|
14
|
+
# @param [Array<String>] headers
|
15
|
+
# @param [Hash] options
|
16
|
+
#
|
17
|
+
# @return [Array<Hash<String, any>>]
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
def self.load(csv, headers:, **options)
|
21
|
+
::CSV.parse(csv, headers: headers, **options).map(&:to_h)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Serialize Array<Hash<String, any>> into CSV
|
25
|
+
#
|
26
|
+
# @param [Array<Hash<String, any>>] obj Array<Hash<String, any>> object
|
27
|
+
# @param [Array<String>] headers
|
28
|
+
# @param [Hash] options
|
29
|
+
#
|
30
|
+
# @return [String]
|
31
|
+
#
|
32
|
+
# @api private
|
33
|
+
def self.dump(obj, headers:, **options)
|
34
|
+
::CSV.generate(**options) do |csv|
|
35
|
+
obj.each do |row|
|
36
|
+
values = []
|
37
|
+
|
38
|
+
headers.each do |header|
|
39
|
+
values << row[header] if row.key?(header)
|
40
|
+
end
|
41
|
+
|
42
|
+
csv << values
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|