lutaml-model 0.5.4 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +58 -21
- data/Gemfile +1 -0
- data/README.adoc +1112 -264
- data/lib/lutaml/model/attribute.rb +33 -10
- data/lib/lutaml/model/choice.rb +56 -0
- data/lib/lutaml/model/config.rb +1 -0
- data/lib/lutaml/model/error/choice_lower_bound_error.rb +9 -0
- data/lib/lutaml/model/error/choice_upper_bound_error.rb +9 -0
- data/lib/lutaml/model/error/import_model_with_root_error.rb +9 -0
- data/lib/lutaml/model/error/incorrect_sequence_error.rb +9 -0
- data/lib/lutaml/model/error/invalid_choice_range_error.rb +20 -0
- data/lib/lutaml/model/error/no_root_mapping_error.rb +9 -0
- data/lib/lutaml/model/error/no_root_namespace_error.rb +9 -0
- data/lib/lutaml/model/error/unknown_sequence_mapping_error.rb +9 -0
- data/lib/lutaml/model/error.rb +8 -0
- data/lib/lutaml/model/json_adapter/standard_json_adapter.rb +6 -1
- data/lib/lutaml/model/key_value_mapping.rb +3 -1
- data/lib/lutaml/model/key_value_mapping_rule.rb +4 -2
- data/lib/lutaml/model/liquefiable.rb +59 -0
- data/lib/lutaml/model/mapping_hash.rb +1 -1
- data/lib/lutaml/model/mapping_rule.rb +15 -2
- data/lib/lutaml/model/schema/xml_compiler.rb +68 -26
- data/lib/lutaml/model/schema_location.rb +7 -0
- data/lib/lutaml/model/sequence.rb +71 -0
- data/lib/lutaml/model/serialize.rb +125 -35
- data/lib/lutaml/model/type/decimal.rb +0 -4
- data/lib/lutaml/model/type/time.rb +3 -3
- data/lib/lutaml/model/utils.rb +19 -15
- data/lib/lutaml/model/validation.rb +12 -1
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/builder/oga.rb +10 -7
- data/lib/lutaml/model/xml_adapter/builder/ox.rb +20 -13
- data/lib/lutaml/model/xml_adapter/element.rb +32 -0
- data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +8 -8
- data/lib/lutaml/model/xml_adapter/oga/element.rb +14 -13
- data/lib/lutaml/model/xml_adapter/oga_adapter.rb +86 -19
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +19 -15
- data/lib/lutaml/model/xml_adapter/xml_document.rb +74 -13
- data/lib/lutaml/model/xml_adapter/xml_element.rb +57 -3
- data/lib/lutaml/model/xml_mapping.rb +49 -7
- data/lib/lutaml/model/xml_mapping_rule.rb +8 -3
- data/lib/lutaml/model.rb +1 -0
- data/lutaml-model.gemspec +5 -0
- data/spec/benchmarks/xml_parsing_benchmark_spec.rb +75 -0
- data/spec/ceramic_spec.rb +39 -0
- data/spec/fixtures/ceramic.rb +23 -0
- data/spec/fixtures/xml/address_example_260.xsd +9 -0
- data/spec/fixtures/xml/user.xsd +10 -0
- data/spec/lutaml/model/cdata_spec.rb +4 -5
- data/spec/lutaml/model/choice_spec.rb +168 -0
- data/spec/lutaml/model/collection_spec.rb +1 -1
- data/spec/lutaml/model/custom_model_spec.rb +6 -7
- data/spec/lutaml/model/custom_serialization_spec.rb +74 -2
- data/spec/lutaml/model/defaults_spec.rb +3 -1
- data/spec/lutaml/model/delegation_spec.rb +7 -5
- data/spec/lutaml/model/enum_spec.rb +35 -0
- data/spec/lutaml/model/group_spec.rb +160 -0
- data/spec/lutaml/model/inheritance_spec.rb +25 -0
- data/spec/lutaml/model/liquefiable_spec.rb +121 -0
- data/spec/lutaml/model/mixed_content_spec.rb +80 -41
- data/spec/lutaml/model/multiple_mapping_spec.rb +22 -10
- data/spec/lutaml/model/schema/xml_compiler_spec.rb +218 -25
- data/spec/lutaml/model/sequence_spec.rb +216 -0
- data/spec/lutaml/model/transformation_spec.rb +230 -0
- data/spec/lutaml/model/type_spec.rb +138 -31
- data/spec/lutaml/model/utils_spec.rb +32 -0
- data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +11 -7
- data/spec/lutaml/model/xml_mapping_rule_spec.rb +51 -0
- data/spec/lutaml/model/xml_mapping_spec.rb +143 -112
- metadata +67 -2
data/README.adoc
CHANGED
@@ -8,19 +8,27 @@ image:https://img.shields.io/gem/v/lutaml-model.svg[RubyGems Version]
|
|
8
8
|
|
9
9
|
== Purpose
|
10
10
|
|
11
|
-
Lutaml::Model is
|
12
|
-
|
13
|
-
an adapter pattern to support multiple libraries for each format, providing
|
14
|
-
flexibility and extensibility for your data modeling needs.
|
11
|
+
Lutaml::Model is the Ruby implementation of the LutaML modeling methodology,
|
12
|
+
for:
|
15
13
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
Shale.
|
14
|
+
* creating information models in the LutaML language (or its Ruby DSL)
|
15
|
+
* serializing and deserializing LutaML information models
|
16
|
+
* accessing data instances of LutaML information models
|
17
|
+
* documenting LutaML information models
|
21
18
|
|
22
|
-
|
23
|
-
|
19
|
+
It provides simple, flexible and comprehensive mechanisms for defining
|
20
|
+
information models with attributes and types, and the serialization of them
|
21
|
+
to/from serialization formats including JSON, XML, YAML, and TOML.
|
22
|
+
|
23
|
+
For serialization formats, it uses an adapter pattern to support multiple
|
24
|
+
libraries for each format, providing flexibility and extensibility for your data
|
25
|
+
modeling needs.
|
26
|
+
|
27
|
+
NOTE: The Lutaml::Model modeling Ruby DSL was originally designed to be mostly
|
28
|
+
compatible with the data modeling DSL of https://www.shalerb.org[Shale], a data
|
29
|
+
modeller for Ruby. Lutaml::Model is meant to address advanced needs not
|
30
|
+
currently addressed by Shale. Instructions on how to migrate from Shale to
|
31
|
+
Lutaml::Model are provided in <<migrate-from-shale>>.
|
24
32
|
|
25
33
|
|
26
34
|
== Features
|
@@ -96,38 +104,38 @@ Studio (Model)
|
|
96
104
|
.Modeling relationships of a LutaML Model to serialization models
|
97
105
|
[source]
|
98
106
|
----
|
99
|
-
╔═══════════════════════╗
|
100
|
-
║ LutaML Core Model ║
|
101
|
-
╚═══════════════════════╝
|
102
|
-
|
103
|
-
╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
|
104
|
-
┆ Model ┆
|
105
|
-
┆ │ ┆ ┌────────────────┐
|
106
|
-
┆ ┌────────┴──┐ ┆ │ │
|
107
|
-
┆ │ │ ┆ │ Model │
|
108
|
-
┆ Models Value Types ┆──►│ Transformation │
|
109
|
-
┆ │ │ ┆ │ & │
|
110
|
-
┆ │ │ ┆ │ Mapping Rules │
|
111
|
-
┆ │ ┌──────┴──┐ ┆ │ │
|
112
|
-
┆ │ │ │ ┆ └────────────────┘
|
113
|
-
┆ │ String Integer ┆ │
|
114
|
-
┆ │ Date Float ┆ │
|
115
|
-
┆ │ Time Boolean ┆
|
116
|
-
┆ │ ┆ │
|
117
|
-
┆ └──────┐ ┆ │
|
107
|
+
╔═══════════════════════╗ ╔════════════════════════════╗
|
108
|
+
║ LutaML Core Model ║ ║ Serialization Models ║
|
109
|
+
╚═══════════════════════╝ ╚════════════════════════════╝
|
110
|
+
|
111
|
+
╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
|
112
|
+
┆ Model ┆ ┆ XML Model ┆
|
113
|
+
┆ │ ┆ ┌────────────────┐ ┆ │ ┆
|
114
|
+
┆ ┌────────┴──┐ ┆ │ │ ┆ ┌──────┴──────┐ ┆
|
115
|
+
┆ │ │ ┆ │ Model │ ┆ │ │ ┆
|
116
|
+
┆ Models Value Types ┆──►│ Transformation │ ┆ Models Value Types ┆
|
117
|
+
┆ │ │ ┆ │ & │ ┆ │ │ ┆
|
118
|
+
┆ │ │ ┆ │ Mapping Rules │ ┆ │ │ ┆
|
119
|
+
┆ │ ┌──────┴──┐ ┆ │ │ ┆ ┌────┴────┐ ┌─┴─┐ ┆
|
120
|
+
┆ │ │ │ ┆ └────────────────┘ ┆ │ │ │ │ ┆
|
121
|
+
┆ │ String Integer ┆ │ ┆ Element Value xs:string ┆
|
122
|
+
┆ │ Date Float ┆ │ ┆ Attribute Type xs:date ┆
|
123
|
+
┆ │ Time Boolean ┆ ├──────────►┆ xs:boolean ┆
|
124
|
+
┆ │ ┆ │ ┆ xs:anyURI ┆
|
125
|
+
┆ └──────┐ ┆ │ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
|
118
126
|
┆ │ ┆ │
|
119
|
-
┆ Contains ┆ │
|
120
|
-
┆ more Models ┆ │
|
121
|
-
┆ (recursive) ┆ │
|
122
|
-
┆ ┆ │
|
123
|
-
╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
127
|
+
┆ Contains ┆ │ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
|
128
|
+
┆ more Models ┆ │ ┆ JSON Model ┆
|
129
|
+
┆ (recursive) ┆ │ ┆ │ ┆
|
130
|
+
┆ ┆ │ ┆ ┌──────┴──────┐ ┆
|
131
|
+
╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯ └──────────►┆ │ │ ┆
|
132
|
+
┆ Models Value Types ┆
|
133
|
+
┆ │ │ ┆
|
134
|
+
┆ │ │ ┆
|
135
|
+
┆ ┌────┴───┐ ┌───┴──┐ ┆
|
136
|
+
┆ │ │ │ │ ┆
|
137
|
+
┆ object array number string ┆
|
138
|
+
┆ value boolean null ┆
|
131
139
|
╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
|
132
140
|
----
|
133
141
|
|
@@ -635,6 +643,149 @@ end
|
|
635
643
|
====
|
636
644
|
|
637
645
|
|
646
|
+
=== Sequence within XmlMapping
|
647
|
+
|
648
|
+
The sequence option enforces that the defined components must appear in a specified order.
|
649
|
+
|
650
|
+
NOTE: `sequence` only works within XML and only supports `map_element` mappings.
|
651
|
+
|
652
|
+
.Using the `sequence` keyword to define a set of elements in desired order.
|
653
|
+
[example]
|
654
|
+
====
|
655
|
+
[source,ruby]
|
656
|
+
----
|
657
|
+
class Kiln < Lutaml::Model::Serializable
|
658
|
+
attribute :id, :string
|
659
|
+
attribute :name, :string
|
660
|
+
attribute :type, :string
|
661
|
+
attribute :color, :string
|
662
|
+
|
663
|
+
xml do
|
664
|
+
sequence do
|
665
|
+
map_element :id, to: :id
|
666
|
+
map_element :name, to: :name
|
667
|
+
map_element :type, to: :type
|
668
|
+
map_element :color, to: :color
|
669
|
+
end
|
670
|
+
end
|
671
|
+
end
|
672
|
+
|
673
|
+
class KilnCollection < Lutaml::Model::Serializable
|
674
|
+
attribute :kiln, Kiln, collection: 1..2
|
675
|
+
|
676
|
+
xml do
|
677
|
+
root "collection"
|
678
|
+
map_element "kiln", to: :kiln
|
679
|
+
end
|
680
|
+
end
|
681
|
+
----
|
682
|
+
|
683
|
+
[source,ruby]
|
684
|
+
----
|
685
|
+
> parsed = Kiln.from_xml("<Kiln> <id>1</id> <name>Nick</name> <type>Hard</type> <color>Black</color> </Kiln>")
|
686
|
+
> # parsed.not_to raise_error
|
687
|
+
----
|
688
|
+
====
|
689
|
+
|
690
|
+
|
691
|
+
=== Reusable Classes with Import
|
692
|
+
|
693
|
+
Lutaml lets you create reusable element and attribute collections using `no_root`. These can be imported into other models using:
|
694
|
+
|
695
|
+
- `import_model`: imports both attributes and mappings
|
696
|
+
- `import_model_attributes`: imports only attributes
|
697
|
+
- `import_model_mappings`: imports only mappings
|
698
|
+
|
699
|
+
NOTE: This feature works with XML. Import order determines how elements and attributes are overwritten.
|
700
|
+
|
701
|
+
[example]
|
702
|
+
====
|
703
|
+
[source,ruby]
|
704
|
+
----
|
705
|
+
class GroupOfItems < Lutaml::Model::Serializable
|
706
|
+
attribute :name, :string
|
707
|
+
attribute :type, :string
|
708
|
+
attribute :code, :string
|
709
|
+
|
710
|
+
xml do
|
711
|
+
no_root
|
712
|
+
sequence do
|
713
|
+
map_element "name", to: :name
|
714
|
+
map_element "type", to: :type, namespace: "http://www.example.com", prefix: "ex1"
|
715
|
+
end
|
716
|
+
map_attribute "code", to: :code
|
717
|
+
end
|
718
|
+
end
|
719
|
+
|
720
|
+
class ComplexType < Lutaml::Model::Serializable
|
721
|
+
attribute :tag, AttributeValueType
|
722
|
+
attribute :content, :string
|
723
|
+
attribute :group, :string
|
724
|
+
import_model_attributes GroupOfItems
|
725
|
+
|
726
|
+
xml do
|
727
|
+
root "GroupOfItems"
|
728
|
+
map_attribute "tag", to: :tag
|
729
|
+
map_content to: :content
|
730
|
+
map_element :group, to: :group
|
731
|
+
import_model_mappings GroupOfItems
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
class SimpleType < Lutaml::Model::Serializable
|
736
|
+
import_model GroupOfItems
|
737
|
+
end
|
738
|
+
|
739
|
+
class GenericType < Lutaml::Model::Serializable
|
740
|
+
import_model_mappings GroupOfItems
|
741
|
+
end
|
742
|
+
----
|
743
|
+
|
744
|
+
[source,xml]
|
745
|
+
----
|
746
|
+
<GroupOfItems xmlns:ex1="http://www.example.com">
|
747
|
+
<name>Name</name>
|
748
|
+
<ex1:type>Type</ex1:type>
|
749
|
+
</GroupOfItems>
|
750
|
+
----
|
751
|
+
|
752
|
+
[source,ruby]
|
753
|
+
----
|
754
|
+
> parsed = GroupOfItems.from_xml(xml)
|
755
|
+
> # Lutaml::Model::NoRootMappingError: "GroupOfItems has `no_root`, it allowed only for reusable models"
|
756
|
+
----
|
757
|
+
|
758
|
+
NOTE: Models with `no_root` can only be parsed through **Parent Models**. Direct calling `from_xml` will raise `NoRootMappingError`.
|
759
|
+
And if `namespace` is defined with `no_root`, `NoRootNamespaceError` will raise.
|
760
|
+
|
761
|
+
====
|
762
|
+
|
763
|
+
|
764
|
+
=== Choice
|
765
|
+
|
766
|
+
The `choice` option ensures that elements from the specified range are included.
|
767
|
+
|
768
|
+
NOTE: Attribute-level definitions are supported. This can be used with both key_value and xml mappings.
|
769
|
+
|
770
|
+
.Using the `choice` option to define a set of attributes with a range.
|
771
|
+
[example]
|
772
|
+
====
|
773
|
+
[source,ruby]
|
774
|
+
----
|
775
|
+
class Studio < Lutaml::Model::Serializable
|
776
|
+
choice(min: 1, max: 3) do
|
777
|
+
choice(min: 1, max: 2) do
|
778
|
+
attribute :prefix, :string
|
779
|
+
attribute :forename, :string
|
780
|
+
end
|
781
|
+
|
782
|
+
attribute :completeName, :string
|
783
|
+
end
|
784
|
+
end
|
785
|
+
----
|
786
|
+
====
|
787
|
+
|
788
|
+
|
638
789
|
=== Attribute value validation
|
639
790
|
|
640
791
|
==== General
|
@@ -784,7 +935,7 @@ end
|
|
784
935
|
|
785
936
|
|
786
937
|
|
787
|
-
=== Attribute value
|
938
|
+
=== Attribute value defaults
|
788
939
|
|
789
940
|
Specify default values for attributes using the `default` option.
|
790
941
|
The `default` option can be set to a value or a lambda that returns a value.
|
@@ -820,76 +971,6 @@ end
|
|
820
971
|
The "default behavior" (pun intended) is to not render a default value if
|
821
972
|
the current value is the same as the default value.
|
822
973
|
|
823
|
-
In certain cases, it is necessary to render the default value even if the
|
824
|
-
current value is the same as the default value. This can be achieved by setting
|
825
|
-
the `render_default` option to `true`.
|
826
|
-
|
827
|
-
Syntax:
|
828
|
-
|
829
|
-
[source,ruby]
|
830
|
-
----
|
831
|
-
attribute :name_of_attribute, Type, default: -> { value }, render_default: true
|
832
|
-
----
|
833
|
-
|
834
|
-
.Using the `render_default` option to force encoding the default value
|
835
|
-
[example]
|
836
|
-
====
|
837
|
-
[source,ruby]
|
838
|
-
----
|
839
|
-
class Glaze < Lutaml::Model::Serializable
|
840
|
-
attribute :color, :string, default: -> { 'Clear' }
|
841
|
-
attribute :opacity, :string, default: -> { 'Opaque' }
|
842
|
-
attribute :temperature, :integer, default: -> { 1050 }
|
843
|
-
attribute :firing_time, :integer, default: -> { 60 }
|
844
|
-
|
845
|
-
xml do
|
846
|
-
root "glaze"
|
847
|
-
map_element 'color', to: :color
|
848
|
-
map_element 'opacity', to: :opacity, render_default: true
|
849
|
-
map_attribute 'temperature', to: :temperature
|
850
|
-
map_attribute 'firingTime', to: :firing_time, render_default: true
|
851
|
-
end
|
852
|
-
|
853
|
-
json do
|
854
|
-
map 'color', to: :color
|
855
|
-
map 'opacity', to: :opacity, render_default: true
|
856
|
-
map 'temperature', to: :temperature
|
857
|
-
map 'firingTime', to: :firing_time, render_default: true
|
858
|
-
end
|
859
|
-
end
|
860
|
-
----
|
861
|
-
====
|
862
|
-
|
863
|
-
.Attributes with `render_default: true` are rendered when the value is identical to the default
|
864
|
-
[example]
|
865
|
-
====
|
866
|
-
[source,ruby]
|
867
|
-
----
|
868
|
-
> glaze_new = Glaze.new
|
869
|
-
> puts glaze_new.to_xml
|
870
|
-
# <glaze firingTime="60">
|
871
|
-
# <opacity>Opaque</opacity>
|
872
|
-
# </glaze>
|
873
|
-
> puts glaze_new.to_json
|
874
|
-
# {"firingTime":60,"opacity":"Opaque"}
|
875
|
-
----
|
876
|
-
====
|
877
|
-
|
878
|
-
.Attributes with `render_default: true` with non-default values are rendered
|
879
|
-
[example]
|
880
|
-
====
|
881
|
-
[source,ruby]
|
882
|
-
----
|
883
|
-
> glaze = Glaze.new(color: 'Celadon', opacity: 'Semitransparent', temperature: 1300, firing_time: 90)
|
884
|
-
> puts glaze.to_xml
|
885
|
-
# <glaze color="Celadon" temperature="1300" firingTime="90">
|
886
|
-
# <opacity>Semitransparent</opacity>
|
887
|
-
# </glaze>
|
888
|
-
> puts glaze.to_json
|
889
|
-
# {"color":"Celadon","temperature":1300,"firingTime":90,"opacity":"Semitransparent"}
|
890
|
-
----
|
891
|
-
====
|
892
|
-
|
893
974
|
|
894
975
|
|
895
976
|
=== Attribute as raw string
|
@@ -950,7 +1031,7 @@ defining serialization and deserialization mappings.
|
|
950
1031
|
Serialization model mappings are defined under the `xml`, `json`, `yaml`, and
|
951
1032
|
`toml` blocks.
|
952
1033
|
|
953
|
-
.Using the `xml`, `json`, `yaml`, and `
|
1034
|
+
.Using the `xml`, `json`, `yaml`, `toml` and `key_value` blocks to define serialization mappings
|
954
1035
|
[source,ruby]
|
955
1036
|
----
|
956
1037
|
class Example < Lutaml::Model::Serializable
|
@@ -969,6 +1050,10 @@ class Example < Lutaml::Model::Serializable
|
|
969
1050
|
toml do
|
970
1051
|
# ...
|
971
1052
|
end
|
1053
|
+
|
1054
|
+
key_value do
|
1055
|
+
# ...
|
1056
|
+
end
|
972
1057
|
end
|
973
1058
|
----
|
974
1059
|
|
@@ -1376,72 +1461,15 @@ The following class will parse the XML snippet below:
|
|
1376
1461
|
|
1377
1462
|
[source,ruby]
|
1378
1463
|
----
|
1379
|
-
class
|
1380
|
-
attribute :name, :string
|
1381
|
-
attribute :description, :string
|
1382
|
-
attribute :value, :integer
|
1383
|
-
|
1384
|
-
xml do
|
1385
|
-
root 'example'
|
1386
|
-
map_element 'name', to: :name
|
1387
|
-
map_attribute 'value', to: :value
|
1388
|
-
map_content to: :description
|
1389
|
-
end
|
1390
|
-
end
|
1391
|
-
----
|
1392
|
-
|
1393
|
-
[source,xml]
|
1394
|
-
----
|
1395
|
-
<example value="12"><name>John Doe</name> is my moniker.</example>
|
1396
|
-
----
|
1397
|
-
|
1398
|
-
[source,ruby]
|
1399
|
-
----
|
1400
|
-
> Example.from_xml(xml)
|
1401
|
-
> #<Example:0x0000000104ac7240 @name="John Doe", @description=" is my moniker.", @value=12>
|
1402
|
-
> Example.new(name: "John Doe", description: " is my moniker.", value: 12).to_xml
|
1403
|
-
> #<example value="12"><name>John Doe</name> is my moniker.</example>
|
1404
|
-
----
|
1405
|
-
====
|
1406
|
-
|
1407
|
-
|
1408
|
-
==== Encoding Options in XmlAdapter
|
1409
|
-
|
1410
|
-
XmlAdapter supports the encoding in the following ways:
|
1411
|
-
|
1412
|
-
. When encoding is not passed in to_xml:
|
1413
|
-
** Default encoding is UTF-8.
|
1414
|
-
|
1415
|
-
. When encoding is explicitly passed nil:
|
1416
|
-
** Encoding will be nil, show the HexCode(Nokogiri) or ASCII-8bit(Ox).
|
1417
|
-
|
1418
|
-
. When encoding is passed with some option:
|
1419
|
-
** Encoding option will be selected as passed.
|
1420
|
-
|
1421
|
-
|
1422
|
-
Syntax:
|
1423
|
-
|
1424
|
-
[source,ruby]
|
1425
|
-
----
|
1426
|
-
Example.new(description: " ∑ is my ∏ moniker µ.").to_xml
|
1427
|
-
Example.new(description: " ∑ is my ∏ moniker µ.").to_xml(encoding: nil)
|
1428
|
-
Example.new(description: " ∑ is my ∏ moniker µ.").to_xml(encoding: "ASCII")
|
1429
|
-
----
|
1430
|
-
|
1431
|
-
[example]
|
1432
|
-
====
|
1433
|
-
The following class will parse the XML snippet below:
|
1434
|
-
|
1435
|
-
[source,ruby]
|
1436
|
-
----
|
1437
|
-
class Example < Lutaml::Model::Serializable
|
1464
|
+
class Ceramic < Lutaml::Model::Serializable
|
1438
1465
|
attribute :name, :string
|
1439
1466
|
attribute :description, :string
|
1440
|
-
attribute :
|
1467
|
+
attribute :temperature, :integer
|
1441
1468
|
|
1442
1469
|
xml do
|
1443
|
-
root '
|
1470
|
+
root 'ceramic'
|
1444
1471
|
map_element 'name', to: :name
|
1472
|
+
map_attribute 'temperature', to: :temperature
|
1445
1473
|
map_content to: :description
|
1446
1474
|
end
|
1447
1475
|
end
|
@@ -1449,21 +1477,15 @@ end
|
|
1449
1477
|
|
1450
1478
|
[source,xml]
|
1451
1479
|
----
|
1452
|
-
<
|
1480
|
+
<ceramic temperature="1200"><name>Porcelain Vase</name> with celadon glaze.</ceramic>
|
1453
1481
|
----
|
1454
1482
|
|
1455
1483
|
[source,ruby]
|
1456
1484
|
----
|
1457
|
-
>
|
1458
|
-
> #<
|
1459
|
-
>
|
1460
|
-
> #<
|
1461
|
-
|
1462
|
-
> Example.new(name: "John & Doe", description: " ∑ is my ∏ moniker µ.").to_xml(encoding: nil)
|
1463
|
-
> #<example><name>John & Doe</name> ∑ is my ∏ moniker µ.</example>
|
1464
|
-
|
1465
|
-
> Example.new(name: "John & Doe", description: " ∑ is my ∏ moniker µ.").to_xml(encoding: "ASCII")
|
1466
|
-
> #<example><name>John & Doe</name> ∑ is my ∏ moniker µ.</example>
|
1485
|
+
> Ceramic.from_xml(xml)
|
1486
|
+
> #<Ceramic:0x0000000104ac7240 @name="Porcelain Vase", @description=" with celadon glaze.", @temperature=1200>
|
1487
|
+
> Ceramic.new(name: "Porcelain Vase", description: " with celadon glaze.", temperature: 1200).to_xml
|
1488
|
+
> #<ceramic temperature="1200"><name>Porcelain Vase</name> with celadon glaze.</ceramic>
|
1467
1489
|
----
|
1468
1490
|
====
|
1469
1491
|
|
@@ -1765,6 +1787,99 @@ end
|
|
1765
1787
|
// TODO: How to create mixed content from `#new`?
|
1766
1788
|
|
1767
1789
|
|
1790
|
+
[[ordered-content]]
|
1791
|
+
==== Ordered content
|
1792
|
+
|
1793
|
+
`ordered: true` maintains the order of **XML Elements**, while `mixed: true` preserves the order of **XML Elements and Content**.
|
1794
|
+
|
1795
|
+
NOTE: When both options are used, `mixed: true` takes precedence.
|
1796
|
+
|
1797
|
+
To specify ordered content, the `ordered: true` option needs to be set at the
|
1798
|
+
`xml` block's `root` method.
|
1799
|
+
|
1800
|
+
Syntax:
|
1801
|
+
|
1802
|
+
[source,ruby]
|
1803
|
+
----
|
1804
|
+
xml do
|
1805
|
+
root 'xml_element_name', ordered: true
|
1806
|
+
end
|
1807
|
+
----
|
1808
|
+
|
1809
|
+
.Applying `ordered` to treat root as ordered content
|
1810
|
+
[example]
|
1811
|
+
====
|
1812
|
+
|
1813
|
+
[source,ruby]
|
1814
|
+
----
|
1815
|
+
class RootOrderedContent < Lutaml::Model::Serializable
|
1816
|
+
attribute :bold, :string
|
1817
|
+
attribute :italic, :string
|
1818
|
+
attribute :underline, :string
|
1819
|
+
|
1820
|
+
xml do
|
1821
|
+
root "RootOrderedContent", ordered: true
|
1822
|
+
map_element :bold, to: :bold
|
1823
|
+
map_element :italic, to: :italic
|
1824
|
+
map_element :underline, to: :underline
|
1825
|
+
end
|
1826
|
+
end
|
1827
|
+
----
|
1828
|
+
|
1829
|
+
[source,xml]
|
1830
|
+
----
|
1831
|
+
<RootOrderedContent>
|
1832
|
+
<underline>Moon</underline>
|
1833
|
+
<italic>384,400 km</italic>
|
1834
|
+
<bold>bell</bold>
|
1835
|
+
</RootOrderedContent>
|
1836
|
+
----
|
1837
|
+
|
1838
|
+
[source,ruby]
|
1839
|
+
----
|
1840
|
+
> instance = RootOrderedContent.from_xml(xml)
|
1841
|
+
> #<RootOrderedContent:0x0000000104ac7240 @bold="bell", @italic="384,400 km", @underline="Moon">
|
1842
|
+
> instance.to_xml
|
1843
|
+
> #<RootOrderedContent><underline>Moon</underline><italic>384,400 km</italic><bold>bell</bold></RootOrderedContent>
|
1844
|
+
----
|
1845
|
+
|
1846
|
+
**Without Ordered True:**
|
1847
|
+
|
1848
|
+
[source,ruby]
|
1849
|
+
----
|
1850
|
+
class RootOrderedContent < Lutaml::Model::Serializable
|
1851
|
+
attribute :bold, :string
|
1852
|
+
attribute :italic, :string
|
1853
|
+
attribute :underline, :string
|
1854
|
+
|
1855
|
+
xml do
|
1856
|
+
root "RootOrderedContent"
|
1857
|
+
map_element :bold, to: :bold
|
1858
|
+
map_element :italic, to: :italic
|
1859
|
+
map_element :underline, to: :underline
|
1860
|
+
end
|
1861
|
+
end
|
1862
|
+
----
|
1863
|
+
|
1864
|
+
[source,xml]
|
1865
|
+
----
|
1866
|
+
<RootOrderedContent>
|
1867
|
+
<underline>Moon</underline>
|
1868
|
+
<italic>384,400 km</italic>
|
1869
|
+
<bold>bell</bold>
|
1870
|
+
</RootOrderedContent>
|
1871
|
+
----
|
1872
|
+
|
1873
|
+
[source,ruby]
|
1874
|
+
----
|
1875
|
+
> instance = RootOrderedContent.from_xml(xml)
|
1876
|
+
> #<RootOrderedContent:0x0000000104ac7240 @bold="bell", @italic="384,400 km", @underline="Moon">
|
1877
|
+
> instance.to_xml
|
1878
|
+
> #<RootOrderedContent>\n <bold>bell</bold>\n <italic>384,400 km</italic>\n <underline>Moon</underline>\n</RootOrderedContent>
|
1879
|
+
----
|
1880
|
+
====
|
1881
|
+
|
1882
|
+
|
1768
1883
|
[[xml-schema-location]]
|
1769
1884
|
==== Automatic support of `xsi:schemaLocation`
|
1770
1885
|
|
@@ -1803,88 +1918,427 @@ The following snippet shows how `xsi:schemaLocation` is used in an XML document:
|
|
1803
1918
|
</cera:Ceramic>
|
1804
1919
|
----
|
1805
1920
|
|
1806
|
-
LutaML::Model supports the `xsi:schemaLocation` attribute in all XML
|
1807
|
-
serializations by default, through the `schema_location` attribute on the model
|
1808
|
-
instance object.
|
1921
|
+
LutaML::Model supports the `xsi:schemaLocation` attribute in all XML
|
1922
|
+
serializations by default, through the `schema_location` attribute on the model
|
1923
|
+
instance object.
|
1924
|
+
|
1925
|
+
.Retrieving and setting the `xsi:schemaLocation` attribute in XML serialization
|
1926
|
+
[example]
|
1927
|
+
====
|
1928
|
+
In this example, the `xsi:schemaLocation` attribute will be automatically
|
1929
|
+
supplied without the explicit need to define in the model, and allows for
|
1930
|
+
round-trip serialization.
|
1931
|
+
|
1932
|
+
[source,ruby]
|
1933
|
+
----
|
1934
|
+
class Ceramic < Lutaml::Model::Serializable
|
1935
|
+
attribute :type, :string
|
1936
|
+
attribute :glaze, :string
|
1937
|
+
attribute :color, :string
|
1938
|
+
|
1939
|
+
xml do
|
1940
|
+
root 'Ceramic'
|
1941
|
+
namespace 'http://example.com/ceramic', 'cera'
|
1942
|
+
map_element 'Type', to: :type, namespace: :inherit
|
1943
|
+
map_element 'Glaze', to: :glaze
|
1944
|
+
map_attribute 'color', to: :color, namespace: 'http://example.com/color', prefix: 'clr'
|
1945
|
+
end
|
1946
|
+
end
|
1947
|
+
|
1948
|
+
xml_content = <<~HERE
|
1949
|
+
<cera:Ceramic
|
1950
|
+
xmlns:cera="http://example.com/ceramic"
|
1951
|
+
xmlns:clr="http://example.com/color"
|
1952
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
1953
|
+
clr:color="navy-blue"
|
1954
|
+
xsi:schemaLocation="
|
1955
|
+
http://example.com/ceramic http://example.com/ceramic.xsd
|
1956
|
+
http://example.com/color http://example.com/color.xsd
|
1957
|
+
">
|
1958
|
+
<cera:Type>Porcelain</cera:Type>
|
1959
|
+
<Glaze>Clear</Glaze>
|
1960
|
+
</cera:Ceramic>
|
1961
|
+
HERE
|
1962
|
+
----
|
1963
|
+
|
1964
|
+
[source,ruby]
|
1965
|
+
----
|
1966
|
+
> c = Ceramic.from_xml(xml_content)
|
1967
|
+
=>
|
1968
|
+
#<Ceramic:0x00000001222bdd60
|
1969
|
+
...
|
1970
|
+
> schema_loc = c.schema_location
|
1971
|
+
#<Lutaml::Model::SchemaLocation:0x0000000122773760
|
1972
|
+
...
|
1973
|
+
> schema_loc
|
1974
|
+
=>
|
1975
|
+
#<Lutaml::Model::SchemaLocation:0x0000000122773760
|
1976
|
+
@namespace="http://www.w3.org/2001/XMLSchema-instance",
|
1977
|
+
@original_schema_location="http://example.com/ceramic http://example.com/ceramic.xsd http://example.com/color http://example.com/color.xsd",
|
1978
|
+
@prefix="xsi",
|
1979
|
+
@schema_location=
|
1980
|
+
[#<Lutaml::Model::Location:0x00000001222bd018 @location="http://example.com/ceramic.xsd", @namespace="http://example.com/ceramic">,
|
1981
|
+
#<Lutaml::Model::Location:0x00000001222bcfc8 @location="http://example.com/color.xsd", @namespace="http://example.com/color">]>
|
1982
|
+
> new_c = Ceramic.new(type: "Porcelain", glaze: "Clear", color: "navy-blue", schema_location: schema_loc).to_xml
|
1983
|
+
> puts new_c
|
1984
|
+
# <cera:Ceramic
|
1985
|
+
# xmlns:cera="http://example.com/ceramic"
|
1986
|
+
# xmlns:clr="http://example.com/color"
|
1987
|
+
# xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
1988
|
+
# clr:color="navy-blue"
|
1989
|
+
# xsi:schemaLocation="
|
1990
|
+
# http://example.com/ceramic http://example.com/ceramic.xsd
|
1991
|
+
# http://example.com/color http://example.com/color.xsd
|
1992
|
+
# ">
|
1993
|
+
# <cera:Type>Porcelain</cera:Type>
|
1994
|
+
# <cera:Glaze>Clear</cera:Glaze>
|
1995
|
+
# </cera:Ceramic>
|
1996
|
+
----
|
1997
|
+
====
|
1998
|
+
|
1999
|
+
NOTE: For details on `xsi:schemaLocation`, please refer to the
|
2000
|
+
https://www.w3.org/TR/xmlschema-1/#xsi_schemaLocation[W3C XML standard].
|
2001
|
+
|
2002
|
+
|
2003
|
+
|
2004
|
+
==== Character encoding
|
2005
|
+
|
2006
|
+
===== General
|
2007
|
+
|
2008
|
+
Lutaml::Model XML adapters use a default encoding of `UTF-8` for both input and
|
2009
|
+
output.
|
2010
|
+
|
2011
|
+
Serialization data to be parsed (deserialization) and serialization data to be
|
2012
|
+
exported (serialization) may be in a different character encoding than the
|
2013
|
+
default encoding used by the Lutaml::Model XML adapter. This mismatch may lead
|
2014
|
+
to incorrect data reading or incompatibilities when exporting data.
|
2015
|
+
|
2016
|
+
The possible values for setting character encoding to are:
|
2017
|
+
|
2018
|
+
* A valid encoding value, e.g. `UTF-8`, `Shift_JIS`, `ASCII`;
|
2019
|
+
|
2020
|
+
* `nil` to use the default encoding of the adapter. The behavior differs based
|
2021
|
+
on the adapter used.
|
2022
|
+
|
2023
|
+
** Nokogiri: `UTF-8`. The encoding is set to the default encoding of the Nokogiri library,
|
2024
|
+
which is `UTF-8`.
|
2025
|
+
|
2026
|
+
** Oga: `UTF-8`. The encoding is set to the default encoding of the Oga library, which
|
2027
|
+
uses `UTF-8`.
|
2028
|
+
|
2029
|
+
** Ox: `ASCII-8bit`. The encoding is set to the default encoding of the Ox library, which uses
|
2030
|
+
`ASCII-8bit`.
|
2031
|
+
|
2032
|
+
When the `encoding` option is not set, the default encoding of `UTF-8` is
|
2033
|
+
used.
|
2034
|
+
|
2035
|
+
|
2036
|
+
===== Serialization character encoding (exporting)
|
2037
|
+
|
2038
|
+
====== General
|
2039
|
+
|
2040
|
+
There are two ways to set the character encoding of the XML document during
|
2041
|
+
serialization:
|
2042
|
+
|
2043
|
+
Instance setting::
|
2044
|
+
Setting the instance-level `encoding` option by setting
|
2045
|
+
`ModelClassInstance.encoding('...')`. This setting only affects serialization.
|
2046
|
+
|
2047
|
+
Per-export setting::
|
2048
|
+
Setting the `encoding` option when calling for serialization action using the
|
2049
|
+
`ModelClassInstance.to_xml(..., encoding: ...)` method.
|
2050
|
+
|
2051
|
+
[[encoding-instance-setting]]
|
2052
|
+
====== Instance setting
|
2053
|
+
|
2054
|
+
The `encoding` value of an instance sets the character encoding of the XML
|
2055
|
+
document during serialization.
|
2056
|
+
|
2057
|
+
Syntax:
|
2058
|
+
|
2059
|
+
[source,ruby]
|
2060
|
+
----
|
2061
|
+
ModelClassInstance.encoding = {encoding_value}
|
2062
|
+
----
|
2063
|
+
|
2064
|
+
Where,
|
2065
|
+
|
2066
|
+
`ModelClassInstance`:: An instance of the class that inherits from
|
2067
|
+
Lutaml::Model::Serializable.
|
2068
|
+
`{encoding_value}`:: The encoding of the output data.
|
2069
|
+
|
2070
|
+
.Character encoding set to instance is reflected in its serialization output
|
2071
|
+
[example]
|
2072
|
+
====
|
2073
|
+
[source,ruby]
|
2074
|
+
----
|
2075
|
+
class JapaneseCeramic < Lutaml::Model::Serializable
|
2076
|
+
attribute :glaze_type, :string
|
2077
|
+
attribute :description, :string
|
2078
|
+
|
2079
|
+
xml do
|
2080
|
+
root 'JapaneseCeramic'
|
2081
|
+
map_attribute 'glazeType', to: :glaze_type
|
2082
|
+
map_element 'description' to: :description
|
2083
|
+
end
|
2084
|
+
end
|
2085
|
+
----
|
2086
|
+
|
2087
|
+
[source,ruby]
|
2088
|
+
----
|
2089
|
+
# Create a new instance with UTF-8 data
|
2090
|
+
> instance = JapaneseCeramic.new(glaze_type: "志野釉", description: "東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)")
|
2091
|
+
#=> #<JapaneseCeramic:0x0000000104ac7240 @glaze_type="志野釉", @description="東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)">
|
2092
|
+
|
2093
|
+
# Set character encoding to Shift_JIS
|
2094
|
+
> instance.encoding = "Shift_JIS"
|
2095
|
+
#=> "Shift_JIS"
|
2096
|
+
|
2097
|
+
# Serialize the instance
|
2098
|
+
> serialization_output = instance.to_xml
|
2099
|
+
#=> #<JapaneseCeramic><glazeType>\x{5FD8}\x{91CE}\x{91C9}</glazeType><description>\x{6771}\x{4EAC}\x{56FD}\x{7ACB}\x{535A}\x{7269}\x{9928}\x{30B3}\x{30EC}\x{30AF}\x{30B7}\x{30E7}\x{30F3}\x{306E}\x{7BC0}\x{8336}\x{7897}\x{300C}\x{6A4B}\x{672C}\x{300D}\x{FF08}\x{6853}\x{5C71}\x{6642}\x{4EE3}\x{FF09}</description></JapaneseCeramic>
|
2100
|
+
|
2101
|
+
# Check character encoding of output
|
2102
|
+
> serialization_output.encoding
|
2103
|
+
#=> "Shift_JIS"
|
2104
|
+
----
|
2105
|
+
====
|
2106
|
+
|
2107
|
+
|
2108
|
+
====== Per-export setting
|
2109
|
+
|
2110
|
+
The `encoding` option is used in the `ModelClass#to_xml(..., encoding: ...)`
|
2111
|
+
call to set the character encoding of the XML document during serialization.
|
2112
|
+
|
2113
|
+
The per-export encoding setting supersedes the instance-level encoding setting.
|
2114
|
+
|
2115
|
+
Syntax:
|
2116
|
+
|
2117
|
+
[source,ruby]
|
2118
|
+
----
|
2119
|
+
ModelClassInstance.to_xml(encoding: {encoding_value})
|
2120
|
+
----
|
2121
|
+
|
2122
|
+
Where,
|
2123
|
+
|
2124
|
+
`ModelClassInstance`:: An instance of the class that inherits from
|
2125
|
+
Lutaml::Model::Serializable.
|
2126
|
+
`{encoding_value}`:: The encoding of the output data.
|
2127
|
+
|
2128
|
+
|
2129
|
+
[example]
|
2130
|
+
====
|
2131
|
+
The following class will parse the XML snippet below:
|
2132
|
+
|
2133
|
+
[source,ruby]
|
2134
|
+
----
|
2135
|
+
class Ceramic < Lutaml::Model::Serializable
|
2136
|
+
attribute :potter, :string
|
2137
|
+
attribute :description, :string
|
2138
|
+
attribute :temperature, :integer
|
2139
|
+
|
2140
|
+
xml do
|
2141
|
+
root 'ceramic'
|
2142
|
+
map_element 'potter', to: :potter
|
2143
|
+
map_content to: :description
|
2144
|
+
end
|
2145
|
+
end
|
2146
|
+
----
|
2147
|
+
|
2148
|
+
[source,xml]
|
2149
|
+
----
|
2150
|
+
<ceramic><potter>John & Jane</potter> A ∑ series of ∏ porcelain µ vases.</ceramic>
|
2151
|
+
----
|
2152
|
+
|
2153
|
+
[source,ruby]
|
2154
|
+
----
|
2155
|
+
# Object with attributes
|
2156
|
+
> ceramic_instance = Ceramic.new(potter: "John & Jane", description: " A ∑ series of ∏ porcelain µ vases.")
|
2157
|
+
> #<Ceramic:0x0000000104ac7240 @potter="John & Jane", @description=" A ∑ series of ∏ porcelain µ vases.">
|
2158
|
+
|
2159
|
+
# Parsing the XML snippet with the default encoding of UTF-8
|
2160
|
+
> ceramic_parsed = Ceramic.from_xml(xml)
|
2161
|
+
> #<Ceramic:0x0000000104ac7242 @potter="John & Jane", @description=" A ∑ series of ∏ porcelain µ vases.">
|
2162
|
+
|
2163
|
+
# Object with attributes is equal to the parsed object
|
2164
|
+
> ceramic_parsed == ceramic_instance
|
2165
|
+
> # true
|
2166
|
+
|
2167
|
+
# Using the default encoding of UTF-8
|
2168
|
+
> ceramic_instance.to_xml
|
2169
|
+
> #<ceramic><potter>John & Jane</potter> A ∑ series of ∏ porcelain µ vases.</ceramic>
|
2170
|
+
|
2171
|
+
# Using the default encoding of the adapter, which is UTF-8 in this case
|
2172
|
+
> ceramic_instance.to_xml(encoding: nil)
|
2173
|
+
> #<ceramic><potter>John & Jane</potter> A ∑ series of ∏ porcelain µ vases.</ceramic>
|
2174
|
+
|
2175
|
+
# Using ASCII encoding
|
2176
|
+
> ceramic_instance.to_xml(encoding: "ASCII")
|
2177
|
+
> #<ceramic><potter>John & Jane</potter> A ∑ series of ∏ porcelain µ vases.</ceramic>
|
2178
|
+
----
|
2179
|
+
====
|
2180
|
+
|
2181
|
+
|
2182
|
+
.Character encoding set at `to_xml` overrides instance encoding
|
2183
|
+
[example]
|
2184
|
+
====
|
2185
|
+
[source,ruby]
|
2186
|
+
----
|
2187
|
+
class JapaneseCeramic < Lutaml::Model::Serializable
|
2188
|
+
attribute :glaze_type, :string
|
2189
|
+
attribute :description, :string
|
2190
|
+
|
2191
|
+
xml do
|
2192
|
+
root 'JapaneseCeramic'
|
2193
|
+
map_attribute 'glazeType', to: :glaze_type
|
2194
|
+
map_element 'description' to: :description
|
2195
|
+
end
|
2196
|
+
end
|
2197
|
+
----
|
2198
|
+
|
2199
|
+
[source,ruby]
|
2200
|
+
----
|
2201
|
+
# Create a new instance with UTF-8 data
|
2202
|
+
> instance = JapaneseCeramic.new(glaze_type: "志野釉", description: "東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)")
|
2203
|
+
#=> #<JapaneseCeramic:0x0000000104ac7240 @glaze_type="志野釉", @description="東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)">
|
2204
|
+
|
2205
|
+
# Set character encoding to Shift_JIS
|
2206
|
+
> instance.encoding = "Shift_JIS"
|
2207
|
+
#=> "Shift_JIS"
|
2208
|
+
|
2209
|
+
# Serialize the instance
|
2210
|
+
> serialization_output = instance.to_xml(encoding: "UTF-8")
|
2211
|
+
#=> #<JapaneseCeramic><glazeType>志野釉</glazeType><description>東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)</description></JapaneseCeramic>
|
2212
|
+
|
2213
|
+
# Check character encoding of output
|
2214
|
+
> serialization_output.encoding
|
2215
|
+
#=> "UTF-8"
|
2216
|
+
----
|
2217
|
+
====
|
2218
|
+
|
2219
|
+
|
2220
|
+
===== Deserialization character encoding (parsing)
|
2221
|
+
|
2222
|
+
The character encoding of the XML document being parsed is specified using the
|
2223
|
+
`encoding` option when the `ModelClass.from_{format}(...)` is called.
|
2224
|
+
|
2225
|
+
Syntax:
|
2226
|
+
|
2227
|
+
[source,ruby]
|
2228
|
+
----
|
2229
|
+
ModelClass.from_{format}(string_in_format, encoding: {encoding_value})
|
2230
|
+
----
|
2231
|
+
|
2232
|
+
Where,
|
2233
|
+
|
2234
|
+
`ModelClass`:: The class that inherits from Lutaml::Model::Serializable.
|
2235
|
+
`{format}`:: The format of the input data, e.g. `xml`, `json`, `yaml`, `toml`.
|
2236
|
+
`string_in_format`:: The input data in the specified format.
|
2237
|
+
`{encoding_value}`:: The encoding of the input data.
|
2238
|
+
|
2239
|
+
|
2240
|
+
.Setting the `encoding` option during parsing data not encoded in the default encoding (UTF-8)
|
2241
|
+
[example]
|
2242
|
+
====
|
2243
|
+
Using the definition of `JapaneseCeramic` at <<encoding-instance-setting>>.
|
2244
|
+
|
2245
|
+
This XML snippet is in Shift-JIS.
|
2246
|
+
|
2247
|
+
[source,xml]
|
2248
|
+
----
|
2249
|
+
<JapaneseCeramic>
|
2250
|
+
<glazeType>\x{5FD8}\x{91CE}\x{91C9}</glazeType>
|
2251
|
+
<description>\x{6771}\x{4EAC}\x{56FD}\x{7ACB}\x{535A}\x{7269}\x{9928}\x{30B3}\x{30EC}\x{30AF}\x{30B7}\x{30E7}\x{30F3}\x{306E}\x{7BC0}\x{8336}\x{7897}\x{300C}\x{6A4B}\x{672C}\x{300D}\x{FF08}\x{6853}\x{5C71}\x{6642}\x{4EE3}\x{FF09}</description>
|
2252
|
+
</JapaneseCeramic>
|
2253
|
+
----
|
2254
|
+
|
2255
|
+
[source,ruby]
|
2256
|
+
----
|
2257
|
+
# Parse the XML snippet with the encoding of Shift_JIS
|
2258
|
+
> instance = JapaneseCeramic.from_xml(xml, encoding: "Shift_JIS")
|
2259
|
+
#=> #<JapaneseCeramic:0x0000000104ac7240 @glaze_type="志野釉", @description="東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)">
|
2260
|
+
|
2261
|
+
# Check character encoding of the instance
|
2262
|
+
> instance.encoding
|
2263
|
+
#=> "Shift_JIS"
|
2264
|
+
|
2265
|
+
# Serialize the instance using UTF-8
|
2266
|
+
> serialization_output = instance.to_xml(encoding: "UTF-8")
|
2267
|
+
#=> #<JapaneseCeramic><glazeType>志野釉</glazeType><description>東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)</description></JapaneseCeramic>
|
2268
|
+
> serialization_output.encoding
|
2269
|
+
#=> "UTF-8"
|
2270
|
+
----
|
2271
|
+
====
|
2272
|
+
|
2273
|
+
.When the `encoding` option is not set, the default encoding of the adapter is used
|
2274
|
+
[example]
|
2275
|
+
====
|
2276
|
+
Using the definition of `JapaneseCeramic` at <<encoding-instance-setting>>.
|
2277
|
+
|
2278
|
+
This XML snippet is in UTF-8.
|
2279
|
+
|
2280
|
+
[source,xml]
|
2281
|
+
----
|
2282
|
+
<JapaneseCeramic>
|
2283
|
+
<glazeType>志野釉</glazeType>
|
2284
|
+
<description>東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)</description>
|
2285
|
+
</JapaneseCeramic>
|
2286
|
+
----
|
2287
|
+
|
2288
|
+
In adapters that use a default encoding of `UTF-8`, the content is parsed
|
2289
|
+
properly.
|
2290
|
+
|
2291
|
+
[source,ruby]
|
2292
|
+
----
|
2293
|
+
> instance = JapaneseCeramic.from_xml(xml, encoding: nil)
|
2294
|
+
#=> #<JapaneseCeramic:0x0000000104ac7240 @glaze_type="志野釉", @description="東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)">
|
2295
|
+
> instance.encoding
|
2296
|
+
#=> "UTF-8"
|
2297
|
+
> serialization_output = instance.to_xml
|
2298
|
+
#=> #<JapaneseCeramic><glazeType>志野釉</glazeType><description>東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)</description></JapaneseCeramic>
|
2299
|
+
> serialization_output.encoding
|
2300
|
+
#=> "UTF-8"
|
2301
|
+
----
|
2302
|
+
|
2303
|
+
In adapters that use a default encoding of `ASCII-8bit`, the content becomes
|
2304
|
+
malformed.
|
2305
|
+
|
2306
|
+
[source,ruby]
|
2307
|
+
----
|
2308
|
+
> instance = JapaneseCeramic.from_xml(xml, encoding: nil)
|
2309
|
+
#=> #<JapaneseCeramic:0x0000000104ac7240 @glaze_type="�菑�", @description="�東京国立博物館コレクションの篠茶碗�橋本�桃山時代�">
|
2310
|
+
> instance.encoding
|
2311
|
+
#=> "ASCII-8bit"
|
2312
|
+
> serialization_output = instance.to_xml
|
2313
|
+
#=> #<JapaneseCeramic><glazeType>�菑�</glazeType><description>�東京国立博物館コレクションの篠茶碗�橋本�桃山時代�</description></JapaneseCeramic>
|
2314
|
+
> serialization_output.encoding
|
2315
|
+
#=> "ASCII-8bit"
|
2316
|
+
----
|
2317
|
+
====
|
2318
|
+
|
1809
2319
|
|
1810
|
-
.
|
2320
|
+
.Using an invalid encoding to deserialize causes data corruption
|
1811
2321
|
[example]
|
1812
2322
|
====
|
1813
|
-
|
1814
|
-
supplied without the explicit need to define in the model, and allows for
|
1815
|
-
round-trip serialization.
|
1816
|
-
|
1817
|
-
[source,ruby]
|
1818
|
-
----
|
1819
|
-
class Ceramic < Lutaml::Model::Serializable
|
1820
|
-
attribute :type, :string
|
1821
|
-
attribute :glaze, :string
|
1822
|
-
attribute :color, :string
|
2323
|
+
Using the definition of `JapaneseCeramic` at <<encoding-instance-setting>>.
|
1823
2324
|
|
1824
|
-
|
1825
|
-
root 'Ceramic'
|
1826
|
-
namespace 'http://example.com/ceramic', 'cera'
|
1827
|
-
map_element 'Type', to: :type, namespace: :inherit
|
1828
|
-
map_element 'Glaze', to: :glaze
|
1829
|
-
map_attribute 'color', to: :color, namespace: 'http://example.com/color', prefix: 'clr'
|
1830
|
-
end
|
1831
|
-
end
|
2325
|
+
This XML snippet is in UTF-8.
|
1832
2326
|
|
1833
|
-
|
1834
|
-
|
1835
|
-
|
1836
|
-
|
1837
|
-
|
1838
|
-
|
1839
|
-
xsi:schemaLocation="
|
1840
|
-
http://example.com/ceramic http://example.com/ceramic.xsd
|
1841
|
-
http://example.com/color http://example.com/color.xsd
|
1842
|
-
">
|
1843
|
-
<cera:Type>Porcelain</cera:Type>
|
1844
|
-
<Glaze>Clear</Glaze>
|
1845
|
-
</cera:Ceramic>
|
1846
|
-
HERE
|
2327
|
+
[source,xml]
|
2328
|
+
----
|
2329
|
+
<JapaneseCeramic>
|
2330
|
+
<glazeType>志野釉</glazeType>
|
2331
|
+
<description>東京国立博物館コレクションの篠茶碗「橋本」(桃山時代)</description>
|
2332
|
+
</JapaneseCeramic>
|
1847
2333
|
----
|
1848
2334
|
|
1849
2335
|
[source,ruby]
|
1850
2336
|
----
|
1851
|
-
>
|
1852
|
-
|
1853
|
-
#<Ceramic:0x00000001222bdd60
|
1854
|
-
...
|
1855
|
-
> schema_loc = c.schema_location
|
1856
|
-
#<Lutaml::Model::SchemaLocation:0x0000000122773760
|
1857
|
-
...
|
1858
|
-
> schema_loc
|
1859
|
-
=>
|
1860
|
-
#<Lutaml::Model::SchemaLocation:0x0000000122773760
|
1861
|
-
@namespace="http://www.w3.org/2001/XMLSchema-instance",
|
1862
|
-
@original_schema_location="http://example.com/ceramic http://example.com/ceramic.xsd http://example.com/color http://example.com/color.xsd",
|
1863
|
-
@prefix="xsi",
|
1864
|
-
@schema_location=
|
1865
|
-
[#<Lutaml::Model::Location:0x00000001222bd018 @location="http://example.com/ceramic.xsd", @namespace="http://example.com/ceramic">,
|
1866
|
-
#<Lutaml::Model::Location:0x00000001222bcfc8 @location="http://example.com/color.xsd", @namespace="http://example.com/color">]>
|
1867
|
-
> new_c = Ceramic.new(type: "Porcelain", glaze: "Clear", color: "navy-blue", schema_location: schema_loc).to_xml
|
1868
|
-
> puts new_c
|
1869
|
-
# <cera:Ceramic
|
1870
|
-
# xmlns:cera="http://example.com/ceramic"
|
1871
|
-
# xmlns:clr="http://example.com/color"
|
1872
|
-
# xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
1873
|
-
# clr:color="navy-blue"
|
1874
|
-
# xsi:schemaLocation="
|
1875
|
-
# http://example.com/ceramic http://example.com/ceramic.xsd
|
1876
|
-
# http://example.com/color http://example.com/color.xsd
|
1877
|
-
# ">
|
1878
|
-
# <cera:Type>Porcelain</cera:Type>
|
1879
|
-
# <cera:Glaze>Clear</cera:Glaze>
|
1880
|
-
# </cera:Ceramic>
|
2337
|
+
> JapaneseCeramic.from_xml(xml, encoding: "Shift_JIS")
|
2338
|
+
#=> #<JapaneseCeramic:0x0000000104ac7240 @glaze_type="�菑���p���P", @description="�東京国立博物館コレクションの篠茶碗�橋本�桃山時代�">
|
1881
2339
|
----
|
1882
2340
|
====
|
1883
2341
|
|
1884
|
-
NOTE: For details on `xsi:schemaLocation`, please refer to the
|
1885
|
-
https://www.w3.org/TR/xmlschema-1/#xsi_schemaLocation[W3C XML standard].
|
1886
|
-
|
1887
|
-
|
1888
2342
|
|
1889
2343
|
=== Key value data models
|
1890
2344
|
|
@@ -3159,6 +3613,91 @@ end
|
|
3159
3613
|
====
|
3160
3614
|
|
3161
3615
|
|
3616
|
+
=== Rendering default values (forced rendering of default values)
|
3617
|
+
|
3618
|
+
By default, attributes with default values are not rendered if the current value
|
3619
|
+
is the same as the default value.
|
3620
|
+
|
3621
|
+
In certain cases, it is necessary to render the default value even if the
|
3622
|
+
current value is the same as the default value. This is achieved by setting the
|
3623
|
+
`render_default` option to `true`.
|
3624
|
+
|
3625
|
+
Syntax:
|
3626
|
+
|
3627
|
+
[source,ruby]
|
3628
|
+
----
|
3629
|
+
attribute :name_of_attribute, Type, default: -> { value }
|
3630
|
+
|
3631
|
+
xml do
|
3632
|
+
map_element 'name_of_attribute', to: :name_of_attribute, render_default: true
|
3633
|
+
map_attribute 'name_of_attribute', to: :name_of_attribute, render_default: true
|
3634
|
+
end
|
3635
|
+
|
3636
|
+
json | yaml | toml | key_value do
|
3637
|
+
map 'name_of_attribute', to: :name_of_attribute, render_default: true
|
3638
|
+
end
|
3639
|
+
----
|
3640
|
+
|
3641
|
+
.Using the `render_default` option to force encoding the default value
|
3642
|
+
[example]
|
3643
|
+
====
|
3644
|
+
[source,ruby]
|
3645
|
+
----
|
3646
|
+
class Glaze < Lutaml::Model::Serializable
|
3647
|
+
attribute :color, :string, default: -> { 'Clear' }
|
3648
|
+
attribute :opacity, :string, default: -> { 'Opaque' }
|
3649
|
+
attribute :temperature, :integer, default: -> { 1050 }
|
3650
|
+
attribute :firing_time, :integer, default: -> { 60 }
|
3651
|
+
|
3652
|
+
xml do
|
3653
|
+
root "glaze"
|
3654
|
+
map_element 'color', to: :color
|
3655
|
+
map_element 'opacity', to: :opacity, render_default: true
|
3656
|
+
map_attribute 'temperature', to: :temperature
|
3657
|
+
map_attribute 'firingTime', to: :firing_time, render_default: true
|
3658
|
+
end
|
3659
|
+
|
3660
|
+
json do
|
3661
|
+
map 'color', to: :color
|
3662
|
+
map 'opacity', to: :opacity, render_default: true
|
3663
|
+
map 'temperature', to: :temperature
|
3664
|
+
map 'firingTime', to: :firing_time, render_default: true
|
3665
|
+
end
|
3666
|
+
end
|
3667
|
+
----
|
3668
|
+
====
|
3669
|
+
|
3670
|
+
.Attributes with `render_default: true` are rendered when the value is identical to the default
|
3671
|
+
[example]
|
3672
|
+
====
|
3673
|
+
[source,ruby]
|
3674
|
+
----
|
3675
|
+
> glaze_new = Glaze.new
|
3676
|
+
> puts glaze_new.to_xml
|
3677
|
+
# <glaze firingTime="60">
|
3678
|
+
# <opacity>Opaque</opacity>
|
3679
|
+
# </glaze>
|
3680
|
+
> puts glaze_new.to_json
|
3681
|
+
# {"firingTime":60,"opacity":"Opaque"}
|
3682
|
+
----
|
3683
|
+
====
|
3684
|
+
|
3685
|
+
.Attributes with `render_default: true` with non-default values are rendered
|
3686
|
+
[example]
|
3687
|
+
====
|
3688
|
+
[source,ruby]
|
3689
|
+
----
|
3690
|
+
> glaze = Glaze.new(color: 'Celadon', opacity: 'Semitransparent', temperature: 1300, firing_time: 90)
|
3691
|
+
> puts glaze.to_xml
|
3692
|
+
# <glaze color="Celadon" temperature="1300" firingTime="90">
|
3693
|
+
# <opacity>Semitransparent</opacity>
|
3694
|
+
# </glaze>
|
3695
|
+
> puts glaze.to_json
|
3696
|
+
# {"color":"Celadon","temperature":1300,"firingTime":90,"opacity":"Semitransparent"}
|
3697
|
+
----
|
3698
|
+
====
|
3699
|
+
|
3700
|
+
|
3162
3701
|
|
3163
3702
|
=== Advanced attribute mapping
|
3164
3703
|
|
@@ -3338,6 +3877,63 @@ end
|
|
3338
3877
|
NOTE: The corresponding keyword used by Shale is `receiver:` instead of
|
3339
3878
|
`delegate:`.
|
3340
3879
|
|
3880
|
+
=== Value Transformations
|
3881
|
+
|
3882
|
+
The `transform` option allows defining import/export transformations at both attribute and mapping levels:
|
3883
|
+
|
3884
|
+
[source,ruby]
|
3885
|
+
----
|
3886
|
+
class Person < Lutaml::Model::Serializable
|
3887
|
+
# Attribute-level transformation
|
3888
|
+
attribute :name, :string, transform: {
|
3889
|
+
export: ->(value) { value.upcase },
|
3890
|
+
import: ->(value) { value.downcase }
|
3891
|
+
}
|
3892
|
+
|
3893
|
+
# Mapping-level transformation in JSON format
|
3894
|
+
json do
|
3895
|
+
map "fullName", to: :name, transform: {
|
3896
|
+
export: ->(value) { "Dr. #{value}" },
|
3897
|
+
import: ->(value) { value.gsub("Dr. ", "") }
|
3898
|
+
}
|
3899
|
+
end
|
3900
|
+
|
3901
|
+
# Mapping-level transformation in XML format
|
3902
|
+
xml do
|
3903
|
+
map "full-name", to: :name, transform: {
|
3904
|
+
export: ->(value) { "Dr. #{value}" },
|
3905
|
+
import: ->(value) { value.gsub("Dr. ", "") }
|
3906
|
+
}
|
3907
|
+
end
|
3908
|
+
end
|
3909
|
+
----
|
3910
|
+
|
3911
|
+
The transformation precedence is:
|
3912
|
+
|
3913
|
+
1. Mapping-level transformation if defined
|
3914
|
+
2. Attribute-level transformation if no mapping transformation exists
|
3915
|
+
|
3916
|
+
This allows flexible value transformations without needing format-specific custom methods:
|
3917
|
+
|
3918
|
+
[source,ruby]
|
3919
|
+
----
|
3920
|
+
person = Person.new(name: "john")
|
3921
|
+
|
3922
|
+
# Uses mapping transformation
|
3923
|
+
person.to_json # => {"fullName": "Dr. john"}
|
3924
|
+
|
3925
|
+
Person.from_json({ "fullName" => "Dr. john"}.to_json).name # => john
|
3926
|
+
|
3927
|
+
# Uses attribute transformation when no mapping exists
|
3928
|
+
person.to_yaml # => name: "JOHN"
|
3929
|
+
----
|
3930
|
+
|
3931
|
+
The `transform` option supports:
|
3932
|
+
|
3933
|
+
* `export`: Transform value during serialization
|
3934
|
+
* `import`: Transform value during deserialization
|
3935
|
+
* Collections with array transformations
|
3936
|
+
* Chaining of attribute and mapping transformations
|
3341
3937
|
|
3342
3938
|
==== Attribute serialization with custom methods
|
3343
3939
|
|
@@ -3377,15 +3973,22 @@ The following class will parse the XML snippet below:
|
|
3377
3973
|
|
3378
3974
|
[source,ruby]
|
3379
3975
|
----
|
3976
|
+
class Metadata < Lutaml::Model::Serializable
|
3977
|
+
attribute :category, :string
|
3978
|
+
attribute :identifier, :string
|
3979
|
+
end
|
3980
|
+
|
3380
3981
|
class CustomCeramic < Lutaml::Model::Serializable
|
3381
3982
|
attribute :name, :string
|
3382
3983
|
attribute :size, :integer
|
3383
3984
|
attribute :description, :string
|
3985
|
+
attribute :metadata, Metadata
|
3384
3986
|
|
3385
3987
|
xml do
|
3386
3988
|
map_element "Name", to: :name, with: { to: :name_to_xml, from: :name_from_xml }
|
3387
3989
|
map_attribute "Size", to: :size, with: { to: :size_to_xml, from: :size_from_xml }
|
3388
3990
|
map_content with: { to: :description_to_xml, from: :description_from_xml }
|
3991
|
+
map_element :metadata, to: :metadata, with: { to: :metadata_to_xml, from: :metadata_from_xml }
|
3389
3992
|
end
|
3390
3993
|
|
3391
3994
|
def name_to_xml(model, parent, doc)
|
@@ -3413,6 +4016,26 @@ class CustomCeramic < Lutaml::Model::Serializable
|
|
3413
4016
|
def description_from_xml(model, value)
|
3414
4017
|
model.description = value.join.strip.sub(/^XML Description: /, "")
|
3415
4018
|
end
|
4019
|
+
|
4020
|
+
def metadata_to_xml(model, parent, doc)
|
4021
|
+
metadata_el = doc.create_element("metadata")
|
4022
|
+
category_el = doc.create_element("category")
|
4023
|
+
identifier_el = doc.create_element("identifier")
|
4024
|
+
|
4025
|
+
doc.add_text(category_el, model.metadata.category)
|
4026
|
+
doc.add_text(identifier_el, model.metadata.identifier)
|
4027
|
+
|
4028
|
+
doc.add_element(metadata_el, category_el)
|
4029
|
+
doc.add_element(metadata_el, identifier_el)
|
4030
|
+
doc.add_element(parent, metadata_el)
|
4031
|
+
end
|
4032
|
+
|
4033
|
+
def metadata_from_xml(model, value)
|
4034
|
+
model.metadata ||= Metadata.new
|
4035
|
+
|
4036
|
+
model.metadata.category = value["elements"]["category"].text
|
4037
|
+
model.metadata.identifier = value["elements"]["identifier"].text
|
4038
|
+
end
|
3416
4039
|
end
|
3417
4040
|
----
|
3418
4041
|
|
@@ -3421,6 +4044,10 @@ end
|
|
3421
4044
|
<CustomCeramic Size="15">
|
3422
4045
|
<Name>XML Masterpiece: Vase</Name>
|
3423
4046
|
XML Description: A beautiful ceramic vase
|
4047
|
+
<metadata>
|
4048
|
+
<category>Metadata</category>
|
4049
|
+
<identifier>123</identifier>
|
4050
|
+
</metadata>
|
3424
4051
|
</CustomCeramic>
|
3425
4052
|
----
|
3426
4053
|
|
@@ -3432,13 +4059,180 @@ end
|
|
3432
4059
|
@name="Masterpiece: Vase",
|
3433
4060
|
@ordered=nil,
|
3434
4061
|
@size=12,
|
3435
|
-
@description="A beautiful ceramic vase"
|
3436
|
-
|
4062
|
+
@description="A beautiful ceramic vase",
|
4063
|
+
@metadata=#<Metadata:0x0000000105ad52e0 @category="Metadata", @identifier="123">>
|
4064
|
+
> puts CustomCeramic.new(name: "Vase", size: 12, description: "A beautiful vase", metadata: Metadata.new(category: "Glaze", identifier: 15)).to_xml
|
3437
4065
|
# <CustomCeramic Size="15">
|
3438
4066
|
# <Name>XML Masterpiece: Vase</Name>
|
4067
|
+
# <metadata>
|
4068
|
+
# <category>Glaze</category>
|
4069
|
+
# <identifier>15</identifier>
|
4070
|
+
# </metadata>
|
3439
4071
|
# XML Description: A beautiful vase
|
3440
4072
|
# </CustomCeramic>
|
3441
4073
|
----
|
4074
|
+
|
4075
|
+
[source,ruby]
|
4076
|
+
----
|
4077
|
+
def custom_method_from_xml(model, value)
|
4078
|
+
instance = value.node # Lutaml::Model::XmlAdapter::AdapterElement
|
4079
|
+
# OR
|
4080
|
+
instance = value.node.adapter_node # Adapter::Element
|
4081
|
+
|
4082
|
+
xml = instance.to_xml
|
4083
|
+
end
|
4084
|
+
----
|
4085
|
+
|
4086
|
+
When building a model from XML in **custom methods**, if the `value` parameter is a `mapping_hash`, then it allows access to the parsed XML structure through `value.node` which can be converted to an XML string using `to_xml`.
|
4087
|
+
|
4088
|
+
NOTE: For `NokogiriAdapter`, we can also call `to_xml` on `value.node.adapter_node`.
|
4089
|
+
|
4090
|
+
[source,ruby]
|
4091
|
+
----
|
4092
|
+
> value
|
4093
|
+
> # {"text"=>["\n ", "\n ", "\n "], "elements"=>{"category"=>{"text"=>"Metadata"}}}
|
4094
|
+
> value.to_xml
|
4095
|
+
> # undefined_method `to_xml`
|
4096
|
+
|
4097
|
+
> value.node
|
4098
|
+
|
4099
|
+
# Nokogiri Adapter Node
|
4100
|
+
|
4101
|
+
#<Lutaml::Model::XmlAdapter::NokogiriElement:0x0000000107656ed8
|
4102
|
+
# @attributes={},
|
4103
|
+
# @children=
|
4104
|
+
# [#<Lutaml::Model::XmlAdapter::NokogiriElement:0x0000000107656cd0 @attributes={}, @children=[], @default_namespace=nil, @name="text", @namespace_prefix=nil, @text="\n ">,
|
4105
|
+
# #<Lutaml::Model::XmlAdapter::NokogiriElement:0x00000001076569b0
|
4106
|
+
# @attributes={},
|
4107
|
+
# @children=
|
4108
|
+
# [#<Lutaml::Model::XmlAdapter::NokogiriElement:0x00000001076567f8 @attributes={}, @children=[], @default_namespace=nil, @name="text", @namespace_prefix=nil, @text="Metadata">],
|
4109
|
+
# @default_namespace=nil,
|
4110
|
+
# @name="category",
|
4111
|
+
# @namespace_prefix=nil,
|
4112
|
+
# @text="Metadata">,
|
4113
|
+
# #<Lutaml::Model::XmlAdapter::NokogiriElement:0x0000000107656028 @attributes={}, @children=[], @default_namespace=nil, @name="text", @namespace_prefix=nil, @text="\n ">],
|
4114
|
+
# @default_namespace=nil,
|
4115
|
+
# @name="metadata",
|
4116
|
+
# @namespace_prefix=nil,
|
4117
|
+
# @text="\n Metadata\n ">
|
4118
|
+
|
4119
|
+
# Ox Adapter Node
|
4120
|
+
|
4121
|
+
#<Lutaml::Model::XmlAdapter::OxElement:0x0000000107584f78
|
4122
|
+
# @attributes={},
|
4123
|
+
# @children=
|
4124
|
+
# [#<Lutaml::Model::XmlAdapter::OxElement:0x0000000107584e60
|
4125
|
+
# @attributes={},
|
4126
|
+
# @children=[#<Lutaml::Model::XmlAdapter::OxElement:0x0000000107584d48 @attributes={}, @children=[], @default_namespace=nil, @name="text", @namespace_prefix=nil, @text="Metadata">],
|
4127
|
+
# @default_namespace=nil,
|
4128
|
+
# @name="category",
|
4129
|
+
# @namespace_prefix=nil,
|
4130
|
+
# @text="Metadata">],
|
4131
|
+
# @default_namespace=nil,
|
4132
|
+
# @name="metadata",
|
4133
|
+
# @namespace_prefix=nil,
|
4134
|
+
# @text=nil>
|
4135
|
+
|
4136
|
+
# Oga Adapter Node
|
4137
|
+
|
4138
|
+
# <Lutaml::Model::XmlAdapter::Oga::Element:0x0000000107314158
|
4139
|
+
# @attributes={},
|
4140
|
+
# @children=
|
4141
|
+
# [#<Lutaml::Model::XmlAdapter::Oga::Element:0x0000000107314090 @attributes={}, @children=[], @default_namespace=nil, @name="text", @namespace_prefix=nil, @text="\n ">,
|
4142
|
+
# #<Lutaml::Model::XmlAdapter::Oga::Element:0x000000010730fe78
|
4143
|
+
# @attributes={},
|
4144
|
+
# @children=[#<Lutaml::Model::XmlAdapter::Oga::Element:0x000000010730fd88 @attributes={}, @children=[], @default_namespace=nil, @name="text", @namespace_prefix=nil, @text="Metadata">],
|
4145
|
+
# @default_namespace=nil,
|
4146
|
+
# @name="category",
|
4147
|
+
# @namespace_prefix=nil,
|
4148
|
+
# @text="Metadata">,
|
4149
|
+
# #<Lutaml::Model::XmlAdapter::Oga::Element:0x000000010730f8d8 @attributes={}, @children=[], @default_namespace=nil, @name="text", @namespace_prefix=nil, @text="\n ">],
|
4150
|
+
# @default_namespace=nil,
|
4151
|
+
# @name="metadata",
|
4152
|
+
# @namespace_prefix=nil,
|
4153
|
+
# @text="\n Metadata\n ">
|
4154
|
+
|
4155
|
+
> value.node.to_xml
|
4156
|
+
> #<metadata><category>Metadata</category></metadata>
|
4157
|
+
----
|
4158
|
+
====
|
4159
|
+
|
4160
|
+
==== Separate Serialization Model With Custom Methods
|
4161
|
+
|
4162
|
+
[example]
|
4163
|
+
====
|
4164
|
+
The following class will parse the XML snippet below:
|
4165
|
+
|
4166
|
+
[source,ruby]
|
4167
|
+
----
|
4168
|
+
class CustomModelChild
|
4169
|
+
attr_accessor :street, :city
|
4170
|
+
end
|
4171
|
+
|
4172
|
+
class CustomModelChildMapper < Lutaml::Model::Serializable
|
4173
|
+
model CustomModelChild
|
4174
|
+
|
4175
|
+
attribute :street, Lutaml::Model::Type::String
|
4176
|
+
attribute :city, Lutaml::Model::Type::String
|
4177
|
+
|
4178
|
+
xml do
|
4179
|
+
map_element :street, to: :street
|
4180
|
+
map_element :city, to: :city
|
4181
|
+
end
|
4182
|
+
end
|
4183
|
+
|
4184
|
+
class CustomModelParentMapper < Lutaml::Model::Serializable
|
4185
|
+
attribute :first_name, Lutaml::Model::Type::String
|
4186
|
+
attribute :child_mapper, CustomModelChildMapper
|
4187
|
+
|
4188
|
+
xml do
|
4189
|
+
map_element :first_name, to: :first_name
|
4190
|
+
map_element :CustomModelChild,
|
4191
|
+
with: { to: :child_to_xml, from: :child_from_xml }
|
4192
|
+
end
|
4193
|
+
|
4194
|
+
def child_to_xml(model, parent, doc)
|
4195
|
+
child_el = doc.create_element("CustomModelChild")
|
4196
|
+
street_el = doc.create_element("street")
|
4197
|
+
city_el = doc.create_element("city")
|
4198
|
+
|
4199
|
+
doc.add_text(street_el, model.child_mapper.street)
|
4200
|
+
doc.add_text(city_el, model.child_mapper.city)
|
4201
|
+
|
4202
|
+
doc.add_element(child_el, street_el)
|
4203
|
+
doc.add_element(child_el, city_el)
|
4204
|
+
doc.add_element(parent, child_el)
|
4205
|
+
end
|
4206
|
+
|
4207
|
+
def child_from_xml(model, value)
|
4208
|
+
model.child_mapper ||= CustomModelChild.new
|
4209
|
+
|
4210
|
+
model.child_mapper.street = value["elements"]["street"].text
|
4211
|
+
model.child_mapper.city = value["elements"]["city"].text
|
4212
|
+
end
|
4213
|
+
end
|
4214
|
+
----
|
4215
|
+
|
4216
|
+
[source,xml]
|
4217
|
+
----
|
4218
|
+
<CustomModelParent>
|
4219
|
+
<first_name>John</first_name>
|
4220
|
+
<CustomModelChild>
|
4221
|
+
<street>Oxford Street</street>
|
4222
|
+
<city>London</city>
|
4223
|
+
</CustomModelChild>
|
4224
|
+
</CustomModelParent>
|
4225
|
+
----
|
4226
|
+
|
4227
|
+
[source,ruby]
|
4228
|
+
----
|
4229
|
+
> instance = CustomModelParentMapper.from_xml(xml)
|
4230
|
+
> #<CustomModelParent:0x0000000107c9ca68 @child_mapper=#<CustomModelChild:0x0000000107c95218 @city="London", @street="Oxford Street">, @first_name="John">
|
4231
|
+
> CustomModelParentMapper.to_xml(instance)
|
4232
|
+
> #<CustomModelParent><first_name>John</first_name><CustomModelChild><street>Oxford Street</street><city>London</city></CustomModelChild></CustomModelParent>
|
4233
|
+
----
|
4234
|
+
|
4235
|
+
NOTE: For **custom models**, `to_xml` is called on the **mapper class**, not on **model instance**.
|
3442
4236
|
====
|
3443
4237
|
|
3444
4238
|
|
@@ -3598,6 +4392,16 @@ provided, a default directory named `lutaml_models_<timestamp>` is created.
|
|
3598
4392
|
[example]
|
3599
4393
|
`"path/to/directory"`
|
3600
4394
|
|
4395
|
+
`create_files`::: A `boolean` argument (`false` by default) to create files directly in the specified directory as defined by the `output_dir` option.
|
4396
|
+
+
|
4397
|
+
[example]
|
4398
|
+
`create_files: (true | false)`
|
4399
|
+
|
4400
|
+
`load_classes`::: A `boolean` argument (`false` by default) to load generated classes before returning them.
|
4401
|
+
+
|
4402
|
+
[example]
|
4403
|
+
`load_classes: (true | false)`
|
4404
|
+
|
3601
4405
|
`namespace`::: The namespace of the schema. This will be added in the
|
3602
4406
|
`Lutaml::Model::Serializable` file's `xml do` block.
|
3603
4407
|
+
|
@@ -3619,6 +4423,7 @@ link:https://www.w3.org/TR/xmlschema-1/#include[XML Schema specification].
|
|
3619
4423
|
[example]
|
3620
4424
|
`"path/to/schema/directory"`
|
3621
4425
|
|
4426
|
+
NOTE: If both `create_files` and `load_classes` are provided, the `create_files` argument will take priority and generate files without loading them!
|
3622
4427
|
|
3623
4428
|
The generated LutaML models consists of two different kind of Ruby classes
|
3624
4429
|
depending on the XSD schema:
|
@@ -3652,9 +4457,12 @@ options = {
|
|
3652
4457
|
output_dir: 'path/to/directory',
|
3653
4458
|
namespace: 'http://example.com/namespace',
|
3654
4459
|
prefix: "example-prefix",
|
3655
|
-
location: "http://example.com/example.xsd"
|
4460
|
+
location: "http://example.com/example.xsd",
|
3656
4461
|
# or
|
3657
4462
|
# location: "path/to/schema/directory"
|
4463
|
+
create_files: true, # Default: false
|
4464
|
+
# OR
|
4465
|
+
load_classes: true, # Default: false
|
3658
4466
|
}
|
3659
4467
|
|
3660
4468
|
# generates the files in the output_dir | default_dir
|
@@ -3695,6 +4503,7 @@ Lutaml::Model supports the following validation methods:
|
|
3695
4503
|
|
3696
4504
|
* `collection`:: Validates collection size range.
|
3697
4505
|
* `values`:: Validates the value of an attribute from a set of fixed values.
|
4506
|
+
* `choice` :: Validates that attribute specified within defined range
|
3698
4507
|
|
3699
4508
|
[example]
|
3700
4509
|
====
|
@@ -3708,6 +4517,17 @@ class Klin < Lutaml::Model::Serializable
|
|
3708
4517
|
attribute :name, :string
|
3709
4518
|
attribute :degree_settings, :integer, collection: (1..)
|
3710
4519
|
attribute :description, :string, values: %w[one two three]
|
4520
|
+
attribute :id, :integer
|
4521
|
+
attribute :age, :integer
|
4522
|
+
|
4523
|
+
choice(min: 1, max: 1) do
|
4524
|
+
choice(min: 1, max: 2) do
|
4525
|
+
attribute :prefix, :string
|
4526
|
+
attribute :forename, :string
|
4527
|
+
end
|
4528
|
+
|
4529
|
+
attribute :nick_name, :string
|
4530
|
+
end
|
3711
4531
|
|
3712
4532
|
xml do
|
3713
4533
|
map_element 'name', to: :name
|
@@ -3715,26 +4535,30 @@ class Klin < Lutaml::Model::Serializable
|
|
3715
4535
|
end
|
3716
4536
|
end
|
3717
4537
|
|
3718
|
-
klin = Klin.new(name: "Klin", degree_settings: [100, 200, 300], description: "one")
|
4538
|
+
klin = Klin.new(name: "Klin", degree_settings: [100, 200, 300], description: "one", prefix: "Ben")
|
3719
4539
|
klin.validate
|
3720
4540
|
# => []
|
3721
4541
|
|
3722
|
-
klin = Klin.new(name: "Klin", degree_settings: [], description: "four")
|
4542
|
+
klin = Klin.new(name: "Klin", degree_settings: [], description: "four", prefix: "Ben", nick_name: "Smith")
|
3723
4543
|
klin.validate
|
3724
4544
|
# => [
|
3725
4545
|
# #<Lutaml::Model::CollectionSizeError: degree_settings must have at least 1 element>,
|
3726
|
-
# #<Lutaml::Model::ValueError: description must be one of [one, two, three]
|
4546
|
+
# #<Lutaml::Model::ValueError: description must be one of [one, two, three]>,
|
4547
|
+
# #<Lutaml::Model::ChoiceUpperBoundError: Attribute count exceeds the upper bound>
|
3727
4548
|
# ]
|
3728
4549
|
|
3729
4550
|
e = klin.validate!
|
3730
4551
|
# => Lutaml::Model::ValidationError: [
|
3731
4552
|
# degree_settings must have at least 1 element,
|
3732
|
-
# description must be one of [one, two, three]
|
4553
|
+
# description must be one of [one, two, three],
|
4554
|
+
# Attribute count exceeds the upper bound
|
3733
4555
|
# ]
|
3734
4556
|
e.errors
|
3735
4557
|
# => [
|
3736
4558
|
# #<Lutaml::Model::CollectionSizeError: degree_settings must have at least 1 element>,
|
3737
|
-
# #<Lutaml::Model::ValueError: description must be one of [one, two, three]
|
4559
|
+
# #<Lutaml::Model::ValueError: description must be one of [one, two, three]>,
|
4560
|
+
# #<Lutaml::Model::ChoiceUpperBoundError: Attribute count exceeds the upper bound>
|
4561
|
+
# #<Lutaml::Model::ChoiceLowerBoundError: Attribute count is less than lower bound>
|
3738
4562
|
# ]
|
3739
4563
|
----
|
3740
4564
|
====
|
@@ -3771,6 +4595,30 @@ klin.validate
|
|
3771
4595
|
====
|
3772
4596
|
|
3773
4597
|
|
4598
|
+
== Liquid Compatability
|
4599
|
+
|
4600
|
+
`to_liquid` can be used to convert a class that inherit from *Lutaml::Model::Serializable* to `LiquidDrop` to be safely used in liquid templates. The returned drop provides all the attributes defined in the class as methods.
|
4601
|
+
|
4602
|
+
[example]
|
4603
|
+
====
|
4604
|
+
[source,ruby]
|
4605
|
+
----
|
4606
|
+
class Person < Lutaml::Model::Serializable
|
4607
|
+
attribute :name, :string
|
4608
|
+
attribute :age, integer
|
4609
|
+
end
|
4610
|
+
|
4611
|
+
person = Person.new({ name: "John", age: 22 })
|
4612
|
+
person_drop = person.to_liquid
|
4613
|
+
# Person::PersonDrop
|
4614
|
+
|
4615
|
+
puts person_drop.name
|
4616
|
+
# "John"
|
4617
|
+
puts person_drop.age
|
4618
|
+
# 22
|
4619
|
+
----
|
4620
|
+
====
|
4621
|
+
|
3774
4622
|
== Adapters
|
3775
4623
|
|
3776
4624
|
=== General
|