lutaml-model 0.4.0 → 0.5.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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +34 -18
  3. data/README.adoc +166 -8
  4. data/lib/lutaml/model/key_value_mapping.rb +0 -1
  5. data/lib/lutaml/model/key_value_mapping_rule.rb +3 -1
  6. data/lib/lutaml/model/mapping_rule.rb +14 -2
  7. data/lib/lutaml/model/serialize.rb +67 -52
  8. data/lib/lutaml/model/type/decimal.rb +5 -0
  9. data/lib/lutaml/model/version.rb +1 -1
  10. data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +1 -0
  11. data/lib/lutaml/model/xml_adapter/builder/oga.rb +180 -0
  12. data/lib/lutaml/model/xml_adapter/builder/ox.rb +1 -0
  13. data/lib/lutaml/model/xml_adapter/oga/document.rb +20 -0
  14. data/lib/lutaml/model/xml_adapter/oga/element.rb +117 -0
  15. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +77 -44
  16. data/lib/lutaml/model/xml_adapter/xml_document.rb +11 -9
  17. data/lib/lutaml/model/xml_mapping.rb +0 -1
  18. data/lib/lutaml/model/xml_mapping_rule.rb +16 -4
  19. data/spec/address_spec.rb +1 -0
  20. data/spec/fixtures/sample_model.rb +7 -0
  21. data/spec/lutaml/model/custom_model_spec.rb +47 -1
  22. data/spec/lutaml/model/included_spec.rb +192 -0
  23. data/spec/lutaml/model/mixed_content_spec.rb +48 -32
  24. data/spec/lutaml/model/multiple_mapping_spec.rb +329 -0
  25. data/spec/lutaml/model/ordered_content_spec.rb +1 -1
  26. data/spec/lutaml/model/render_nil_spec.rb +3 -0
  27. data/spec/lutaml/model/serializable_spec.rb +1 -1
  28. data/spec/lutaml/model/type/boolean_spec.rb +62 -0
  29. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +11 -11
  30. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +1 -1
  31. data/spec/lutaml/model/xml_adapter_spec.rb +2 -2
  32. data/spec/lutaml/model/xml_mapping_spec.rb +24 -9
  33. data/spec/sample_model_spec.rb +114 -0
  34. metadata +8 -2
@@ -0,0 +1,192 @@
1
+ require "spec_helper"
2
+ require "lutaml/model"
3
+
4
+ module IncludedSpec
5
+ class Base
6
+ include Lutaml::Model::Serialize
7
+
8
+ attribute :text, Lutaml::Model::Type::String
9
+ attribute :id, Lutaml::Model::Type::String
10
+ attribute :name, Lutaml::Model::Type::String
11
+
12
+ xml do
13
+ map_content to: :text
14
+ map_attribute "id", to: :id
15
+ map_element "name", to: :name
16
+ end
17
+ end
18
+
19
+ class Implementation1
20
+ include Lutaml::Model::Serialize
21
+
22
+ attribute :text, Lutaml::Model::Type::String
23
+ attribute :id, Lutaml::Model::Type::String
24
+ attribute :name, Lutaml::Model::Type::String
25
+ attribute :age, Lutaml::Model::Type::Integer
26
+
27
+ xml do
28
+ root "impl_one"
29
+ map_content to: :text
30
+ map_attribute "id", to: :id
31
+ map_element "name", to: :name
32
+ map_element "age", to: :age
33
+ end
34
+ end
35
+
36
+ class Implementation2
37
+ include Lutaml::Model::Serialize
38
+
39
+ attribute :text, Lutaml::Model::Type::String
40
+ attribute :id, Lutaml::Model::Type::String
41
+ attribute :name, Lutaml::Model::Type::String
42
+ attribute :age, Lutaml::Model::Type::Integer
43
+
44
+ xml do
45
+ root "impl_two"
46
+ map_content to: :text
47
+ map_attribute "id", to: :id
48
+ map_element "name", to: :name
49
+ map_element "gender", to: :age
50
+ end
51
+ end
52
+
53
+ module ParentClass
54
+ include Lutaml::Model::Serialize
55
+
56
+ attribute :parent_name, Lutaml::Model::Type::String
57
+ attribute :parent_id, Lutaml::Model::Type::String
58
+
59
+ xml do
60
+ root "parent"
61
+ map_attribute "id", to: :parent_id
62
+ map_element "parent_name", to: :parent_name
63
+ end
64
+ end
65
+
66
+ module ChildClass
67
+ include ParentClass
68
+
69
+ attribute :child_name, Lutaml::Model::Type::String
70
+ attribute :child_type, Lutaml::Model::Type::String
71
+ attribute :child_text, Lutaml::Model::Type::String
72
+
73
+ xml do
74
+ root "child"
75
+ map_attribute "type", to: :child_type
76
+ map_element "child_name", to: :child_name
77
+ map_content to: :child_text
78
+ end
79
+ end
80
+
81
+ class GrandChildClass
82
+ include ChildClass
83
+
84
+ attribute :grandchild_name, Lutaml::Model::Type::String
85
+ attribute :grandchild_version, Lutaml::Model::Type::String
86
+ attribute :grandchild_text, Lutaml::Model::Type::String
87
+
88
+ xml do
89
+ root "grandchild"
90
+ map_attribute "version", to: :grandchild_version
91
+ map_element "grandchild_name", to: :grandchild_name
92
+ map_content to: :grandchild_text
93
+ end
94
+ end
95
+ end
96
+
97
+ RSpec.describe "Included" do
98
+ subject(:impl_object) do
99
+ IncludedSpec::Implementation1.new(
100
+ {
101
+ text: "Some text",
102
+ name: "John Doe",
103
+ id: "foobar",
104
+ age: 30,
105
+ },
106
+ )
107
+ end
108
+
109
+ let(:expected_xml) do
110
+ '<impl_one id="foobar"><name>John Doe</name><age>30</age>Some text</impl_one>'
111
+ end
112
+
113
+ it "uses included module attributes" do
114
+ expect(impl_object.to_xml(pretty: true)).to eq(expected_xml)
115
+ end
116
+
117
+ context "with multiple implementing classes" do
118
+ describe "Implementation1" do
119
+ let(:impl1) { IncludedSpec::Implementation1 }
120
+
121
+ it "has correct mappings" do
122
+ expect(impl1.mappings_for(:xml).mappings.count).to eq(4)
123
+ end
124
+
125
+ it "has correct attributes" do
126
+ expect(impl1.attributes.count).to eq(4)
127
+ end
128
+
129
+ it "has correct model" do
130
+ expect(impl1.model).to eq(impl1)
131
+ end
132
+ end
133
+
134
+ describe "Implementation2" do
135
+ let(:impl2) { IncludedSpec::Implementation2 }
136
+
137
+ it "has correct mappings" do
138
+ expect(impl2.mappings_for(:xml).mappings.count).to eq(4)
139
+ end
140
+
141
+ it "has correct attributes" do
142
+ expect(impl2.attributes.count).to eq(4)
143
+ end
144
+
145
+ it "has correct model" do
146
+ expect(impl2.model).to eq(impl2)
147
+ end
148
+ end
149
+ end
150
+
151
+ context "with nested module inclusion" do
152
+ let(:grandchild) do
153
+ IncludedSpec::GrandChildClass.new(
154
+ parent_name: "Parent Name",
155
+ parent_id: "P123",
156
+ child_name: "Child Name",
157
+ child_type: "Type A",
158
+ child_text: "Child Text",
159
+ grandchild_name: "GrandChild Name",
160
+ grandchild_version: "1.0",
161
+ grandchild_text: "GrandChild Text",
162
+ )
163
+ end
164
+
165
+ it "inherits attributes through the chain" do
166
+ expect(IncludedSpec::GrandChildClass.attributes.keys).to include(
167
+ :parent_name, :parent_id,
168
+ :child_name, :child_type, :child_text,
169
+ :grandchild_name, :grandchild_version, :grandchild_text
170
+ )
171
+ end
172
+
173
+ it "maintains correct XML mappings through inheritance" do
174
+ expected_xml = <<~XML
175
+ <grandchild id="P123" type="Type A" version="1.0">
176
+ <parent_name>Parent Name</parent_name>
177
+ <child_name>Child Name</child_name>
178
+ <grandchild_name>GrandChild Name</grandchild_name>
179
+ GrandChild Text
180
+ </grandchild>
181
+ XML
182
+
183
+ expect(grandchild.to_xml(pretty: true)).to be_equivalent_to(expected_xml)
184
+ end
185
+
186
+ it "preserves separate mapping configurations" do
187
+ expect(IncludedSpec::ParentClass.mappings_for(:xml).root_element).to eq("parent")
188
+ expect(IncludedSpec::ChildClass.mappings_for(:xml).root_element).to eq("child")
189
+ expect(IncludedSpec::GrandChildClass.mappings_for(:xml).root_element).to eq("grandchild")
190
+ end
191
+ end
192
+ end
@@ -524,15 +524,12 @@ RSpec.describe "MixedContent" do
524
524
  describe ".from_xml" do
525
525
  let(:expected_nokogiri_content) { "B <p>R&amp;C</p>\n C <p>J&#x2014;C</p>\n O <p>A &amp; B </p>\n F <p>Z &#xA9;S</p>" }
526
526
  let(:expected_ox_content) { "B <p>R&amp;C</p>\n C <p>J—C</p>\n O <p>A &amp; B </p>\n F <p>Z ©S</p>" }
527
+ let(:expected_oga_content) { "B <p>R&amp;C</p>\n C <p>J—C</p>\n O <p>A &amp; B </p>\n F <p>Z ©S</p>" }
527
528
 
528
529
  it "deserializes special char mixed content correctly" do
529
530
  parsed = MixedContentSpec::SpecialCharContentWithRawAndMixedOption.from_xml(xml)
530
- expected_content = expected_nokogiri_content
531
-
532
- if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
533
- expected_content = expected_ox_content
534
- parsed.special.force_encoding("UTF-8")
535
- end
531
+ expected_content = public_send(:"expected_#{adapter_class.type}_content")
532
+ parsed.special.force_encoding("UTF-8") unless adapter_class == Lutaml::Model::XmlAdapter::NokogiriAdapter
536
533
 
537
534
  expect(parsed.special.strip).to eq(expected_content)
538
535
  end
@@ -562,12 +559,23 @@ RSpec.describe "MixedContent" do
562
559
  XML
563
560
  end
564
561
 
562
+ let(:expected_oga_xml) do
563
+ <<~XML
564
+ <SpecialCharContentWithRawOptionAndMixedOption>
565
+ <special> B &lt;p&gt;R&amp;C&lt;/p&gt;
566
+ C &lt;p&gt;J—C&lt;/p&gt;
567
+ O &lt;p&gt;A &amp; B &lt;/p&gt;
568
+ F &lt;p&gt;Z ©S&lt;/p&gt;
569
+ </special>
570
+ </SpecialCharContentWithRawOptionAndMixedOption>
571
+ XML
572
+ end
573
+
565
574
  it "serializes special char mixed content correctly" do
566
575
  parsed = MixedContentSpec::SpecialCharContentWithRawAndMixedOption.from_xml(xml)
567
576
  serialized = parsed.to_xml
568
577
 
569
- expected_output = adapter_class == Lutaml::Model::XmlAdapter::OxAdapter ? expected_ox_xml : expected_nokogiri_xml
570
- expect(serialized).to be_equivalent_to(expected_output)
578
+ expect(serialized).to be_equivalent_to(send(:"expected_#{adapter_class.type}_xml"))
571
579
  end
572
580
  end
573
581
  end
@@ -590,21 +598,21 @@ RSpec.describe "MixedContent" do
590
598
  it "deserializes special char mixed content correctly" do
591
599
  parsed = MixedContentSpec::SpecialCharContentWithRawAndMixedOption.from_xml(xml)
592
600
 
593
- expected_output = adapter_class == Lutaml::Model::XmlAdapter::OxAdapter ? expected_ox_content : expected_nokogiri_content
601
+ expected_output = adapter_class == Lutaml::Model::XmlAdapter::NokogiriAdapter ? expected_nokogiri_content : expected_ox_content
594
602
  expect(parsed.special.strip).to eq(expected_output)
595
603
  end
596
604
  end
597
605
 
598
606
  describe ".to_xml" do
599
607
  let(:expected_nokogiri_xml) { "B &lt;p&gt;R&lt;/p&gt;" }
608
+ let(:expected_oga_xml) { "B &lt;p&gt;R&amp;C&lt;/p&gt;" }
600
609
  let(:expected_ox_xml) { "B &lt;p&gt;R&amp;amp;C&lt;/p&gt;" }
601
610
 
602
611
  it "serializes special char mixed content correctly" do
603
612
  parsed = MixedContentSpec::SpecialCharContentWithRawAndMixedOption.from_xml(xml)
604
613
  serialized = parsed.to_xml
605
614
 
606
- expected_output = adapter_class == Lutaml::Model::XmlAdapter::OxAdapter ? expected_ox_xml : expected_nokogiri_xml
607
- expect(serialized).to include(expected_output)
615
+ expect(serialized).to include(send(:"expected_#{adapter_class.type}_xml"))
608
616
  end
609
617
  end
610
618
  end
@@ -630,12 +638,13 @@ RSpec.describe "MixedContent" do
630
638
 
631
639
  describe ".to_xml" do
632
640
  let(:expected_xml) { "<TextualSupport>\n <value>&lt;computer security&gt; type of operation specified by an access right</value>\n</TextualSupport>" }
641
+ let(:expected_oga_xml) { "<TextualSupport><value>&lt;computer security&gt; type of operation specified by an access right</value></TextualSupport>" }
633
642
 
634
643
  it "serializes special char mixed content correctly" do
635
644
  parsed = MixedContentSpec::TextualSupport.from_xml(xml)
636
645
  serialized = parsed.to_xml
637
-
638
- expect(serialized.strip).to include(expected_xml)
646
+ expected = adapter_class.type == "oga" ? expected_oga_xml : expected_xml
647
+ expect(serialized.strip).to include(expected)
639
648
  end
640
649
  end
641
650
  end
@@ -679,10 +688,10 @@ RSpec.describe "MixedContent" do
679
688
  parsed = MixedContentSpec::HexCode.from_xml(xml)
680
689
  serialized = parsed.to_xml(encoding: nil)
681
690
 
682
- expected_output = if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
683
- expected_encoding_nil_ox_xml
684
- else
691
+ expected_output = if adapter_class == Lutaml::Model::XmlAdapter::NokogiriAdapter
685
692
  expected_encoding_nil_nokogiri_xml
693
+ else
694
+ expected_encoding_nil_ox_xml
686
695
  end
687
696
 
688
697
  expect(serialized.strip).to include(expected_output)
@@ -715,10 +724,12 @@ RSpec.describe "MixedContent" do
715
724
  it "deserializes SHIFT encoded content incorrectly without explicit encoding option" do
716
725
  parsed = MixedContentSpec::Shift.from_xml(fixture)
717
726
 
718
- expected_content = if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
719
- "\x8E\xE8\x8F\x91\x82\xAB\x89p\x8E\x9A\x82P".force_encoding("UTF-8")
720
- else
727
+ expected_content = if adapter_class == Lutaml::Model::XmlAdapter::NokogiriAdapter
721
728
  "�菑���p���P"
729
+ elsif adapter_class == Lutaml::Model::XmlAdapter::OgaAdapter
730
+ "手書き英字1"
731
+ else
732
+ "\x8E\xE8\x8F\x91\x82\xAB\x89p\x8E\x9A\x82P".force_encoding("UTF-8")
722
733
  end
723
734
 
724
735
  expect(parsed.field).to include(expected_content)
@@ -729,8 +740,8 @@ RSpec.describe "MixedContent" do
729
740
  it "serializes SHIFT-JIS encoding content correctly reading from file" do
730
741
  parsed = MixedContentSpec::Shift.from_xml(fixture, encoding: "Shift_JIS")
731
742
  serialized = parsed.to_xml
732
-
733
- expect(serialized.strip).to eq(fixture.strip)
743
+ expected = adapter_class.type == "oga" ? fixture.gsub(/\s+/, "") : fixture.strip
744
+ expect(serialized.strip).to eq(expected)
734
745
  end
735
746
 
736
747
  it "serializes SHIFT encoded content correctly with explicit encoding option both in parsing and deserializing" do
@@ -780,13 +791,13 @@ RSpec.describe "MixedContent" do
780
791
  it "serializes SHIFT-JIS content incorrectly bcz no encoding provided during parsing" do
781
792
  parsed = MixedContentSpec::Shift.from_xml(fixture)
782
793
  serialized = parsed.to_xml(encoding: "Shift_JIS")
783
-
784
- expected_content = if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
794
+ expected_content = if adapter_class == Lutaml::Model::XmlAdapter::NokogiriAdapter
795
+ "<root>\n <FieldName>&#65533;&#33745;&#65533;&#65533;&#65533;p&#65533;&#65533;&#65533;P</FieldName>\n <FieldName>123456</FieldName>\n</root>"
796
+ elsif adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
785
797
  "<root>\n <FieldName>\x8E菑\x82\xAB\x89p\x8E\x9A\x82P</FieldName>\n <FieldName>123456</FieldName>\n</root>\n"
786
798
  else
787
- "<root>\n <FieldName>&#65533;&#33745;&#65533;&#65533;&#65533;p&#65533;&#65533;&#65533;P</FieldName>\n <FieldName>123456</FieldName>\n</root>"
799
+ "<root><FieldName>手書き英字1</FieldName><FieldName>123456</FieldName></root>".encode("Shift_JIS")
788
800
  end
789
-
790
801
  expect(serialized).to eq(expected_content)
791
802
  end
792
803
 
@@ -831,10 +842,12 @@ RSpec.describe "MixedContent" do
831
842
  it "deserializes latin encoded content incorrectly" do
832
843
  parsed = MixedContentSpec::Latin.from_xml(fixture)
833
844
 
834
- expected_content = if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
835
- ["M\xFCller", "Jos\xE9"]
836
- else
845
+ expected_content = if adapter_class == Lutaml::Model::XmlAdapter::NokogiriAdapter
837
846
  ["M�ller", "Jos�"]
847
+ elsif adapter_class == Lutaml::Model::XmlAdapter::OgaAdapter
848
+ ["Müller", "José"]
849
+ else
850
+ ["M\xFCller", "Jos\xE9"]
838
851
  end
839
852
 
840
853
  expect(parsed.from).to eq(expected_content[0])
@@ -846,8 +859,12 @@ RSpec.describe "MixedContent" do
846
859
  it "serializes latin encoded content correctly" do
847
860
  parsed = MixedContentSpec::Latin.from_xml(fixture, encoding: "ISO-8859-1")
848
861
  serialized = parsed.to_xml
849
-
850
- expect(serialized.strip).to eq("<note>\n <to>Jos\xE9</to>\n <from>M\xFCller</from>\n <heading>Reminder</heading>\n</note>".force_encoding("ISO-8859-1"))
862
+ expected_xml = if adapter_class == Lutaml::Model::XmlAdapter::OgaAdapter
863
+ "<note><to>Jos\xE9</to><from>M\xFCller</from><heading>Reminder</heading></note>"
864
+ else
865
+ "<note>\n <to>Jos\xE9</to>\n <from>M\xFCller</from>\n <heading>Reminder</heading>\n</note>"
866
+ end
867
+ expect(serialized.strip).to eq(expected_xml.force_encoding("ISO-8859-1"))
851
868
  end
852
869
  end
853
870
  end
@@ -868,8 +885,7 @@ RSpec.describe "MixedContent" do
868
885
  it_behaves_like "mixed content behavior", described_class
869
886
  end
870
887
 
871
- # Not implemented yet
872
- xdescribe Lutaml::Model::XmlAdapter::OgaAdapter do
888
+ describe Lutaml::Model::XmlAdapter::OgaAdapter do
873
889
  it_behaves_like "mixed content behavior", described_class
874
890
  end
875
891
  end