lutaml-model 0.5.4 → 0.6.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/.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
|