lutaml-model 0.3.29 → 0.4.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-repos.json +15 -0
  3. data/.github/workflows/dependent-tests.yml +14 -0
  4. data/.rubocop_todo.yml +15 -7
  5. data/README.adoc +6 -0
  6. data/lib/lutaml/model/attribute.rb +6 -2
  7. data/lib/lutaml/model/json_adapter/json_document.rb +1 -1
  8. data/lib/lutaml/model/json_adapter/multi_json_adapter.rb +1 -1
  9. data/lib/lutaml/model/json_adapter/standard_json_adapter.rb +1 -1
  10. data/lib/lutaml/model/serialize.rb +133 -33
  11. data/lib/lutaml/model/toml_adapter/toml_document.rb +1 -1
  12. data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +1 -1
  13. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +1 -1
  14. data/lib/lutaml/model/type/time.rb +4 -4
  15. data/lib/lutaml/model/utils.rb +1 -1
  16. data/lib/lutaml/model/validation.rb +6 -2
  17. data/lib/lutaml/model/version.rb +1 -1
  18. data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +10 -0
  19. data/lib/lutaml/model/xml_adapter/builder/ox.rb +5 -1
  20. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +5 -3
  21. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +1 -1
  22. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +25 -10
  23. data/lib/lutaml/model/xml_adapter/xml_document.rb +12 -24
  24. data/lib/lutaml/model/yaml_adapter/standard_yaml_adapter.rb +1 -1
  25. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +1 -1
  26. data/spec/fixtures/xml/latin_encoding.xml +5 -0
  27. data/spec/fixtures/xml/shift_jis.xml +4 -0
  28. data/spec/lutaml/model/custom_serialization_spec.rb +16 -0
  29. data/spec/lutaml/model/defaults_spec.rb +74 -0
  30. data/spec/lutaml/model/enum_spec.rb +131 -0
  31. data/spec/lutaml/model/mixed_content_spec.rb +190 -7
  32. data/spec/lutaml/model/render_nil_spec.rb +0 -2
  33. data/spec/lutaml/model/serializable_spec.rb +2 -2
  34. data/spec/lutaml/model/xml_mapping_spec.rb +679 -543
  35. metadata +7 -2
@@ -6,23 +6,28 @@ module Lutaml
6
6
  module Model
7
7
  module XmlAdapter
8
8
  class OxAdapter < XmlDocument
9
- def self.parse(xml)
9
+ def self.parse(xml, options = {})
10
+ Ox.default_options = Ox.default_options.merge(encoding: options[:encoding] || "UTF-8")
11
+
10
12
  parsed = Ox.parse(xml)
11
13
  root = OxElement.new(parsed)
12
- new(root)
14
+ new(root, Ox.default_options[:encoding])
13
15
  end
14
16
 
15
17
  def to_xml(options = {})
16
- builder = Builder::Ox.build
17
18
  builder_options = { version: options[:version] }
18
19
 
19
- if options.key?(:encoding)
20
- builder_options[:encoding] = options[:encoding] unless options[:encoding].nil?
21
- else
22
- builder_options[:encoding] = "UTF-8"
23
- end
20
+ builder_options[:encoding] = if options.key?(:encoding)
21
+ options[:encoding]
22
+ elsif options.key?(:parse_encoding)
23
+ options[:parse_encoding]
24
+ else
25
+ "UTF-8"
26
+ end
27
+
28
+ builder = Builder::Ox.build
29
+ builder.xml.instruct(:xml, encoding: options[:parse_encoding])
24
30
 
25
- builder.xml.instruct(:xml, builder_options)
26
31
  if @root.is_a?(Lutaml::Model::XmlAdapter::OxElement)
27
32
  @root.build_xml(builder)
28
33
  elsif ordered?(@root, options)
@@ -34,7 +39,12 @@ module Lutaml
34
39
  end
35
40
 
36
41
  xml_data = builder.xml.to_s
37
- options[:declaration] ? xml_data : xml_data.sub(/\A<\?xml[^>]*\?>\n?/, "")
42
+ if builder_options[:encoding] && xml_data.valid_encoding?
43
+ xml_data = xml_data.encode(builder_options[:encoding])
44
+ end
45
+
46
+ stripped_data = xml_data.lines.drop(1).join
47
+ options[:declaration] ? declaration(options) + stripped_data : stripped_data
38
48
  end
39
49
 
40
50
  private
@@ -145,6 +155,11 @@ module Lutaml
145
155
  build_xml.xml.to_s
146
156
  end
147
157
 
158
+ def inner_xml
159
+ # Ox builder by default, adds a newline at the end, so `chomp` is used
160
+ children.map { |child| child.to_xml.chomp }.join
161
+ end
162
+
148
163
  def build_xml(builder = nil)
149
164
  builder ||= Builder::Ox.build
150
165
  attrs = build_attributes(self)
@@ -7,13 +7,14 @@ module Lutaml
7
7
  module Model
8
8
  module XmlAdapter
9
9
  class XmlDocument
10
- attr_reader :root
10
+ attr_reader :root, :encoding
11
11
 
12
- def initialize(root)
12
+ def initialize(root, encoding = nil)
13
13
  @root = root
14
+ @encoding = encoding
14
15
  end
15
16
 
16
- def self.parse(xml)
17
+ def self.parse(xml, _options = {})
17
18
  raise NotImplementedError, "Subclasses must implement `parse`."
18
19
  end
19
20
 
@@ -150,6 +151,8 @@ module Lutaml
150
151
  value,
151
152
  options.merge({ rule: rule, attribute: attribute }),
152
153
  )
154
+ elsif rule.raw_mapping?
155
+ xml.add_xml_fragment(xml, value)
153
156
  elsif rule.prefix_set?
154
157
  xml.create_and_add_element(rule.name, prefix: prefix) do
155
158
  add_value(xml, value, attribute, cdata: rule.cdata)
@@ -186,10 +189,6 @@ module Lutaml
186
189
  attributes = build_attributes(element,
187
190
  xml_mapping, options).merge(attributes)&.compact
188
191
 
189
- if element.respond_to?(:schema_location) && element.schema_location
190
- attributes.merge!(element.schema_location.to_xml_attributes)
191
- end
192
-
193
192
  prefix = if options.key?(:namespace_prefix)
194
193
  options[:namespace_prefix]
195
194
  elsif xml_mapping.namespace_prefix
@@ -210,7 +209,8 @@ module Lutaml
210
209
  xml)
211
210
  end
212
211
 
213
- xml_mapping.elements.each do |element_rule|
212
+ mappings = xml_mapping.elements + [xml_mapping.raw_mapping].compact
213
+ mappings.each do |element_rule|
214
214
  attribute_def = attribute_definition_for(element, element_rule,
215
215
  mapper_class: mapper_class)
216
216
 
@@ -234,20 +234,9 @@ module Lutaml
234
234
 
235
235
  process_content_mapping(element, xml_mapping.content_mapping,
236
236
  prefixed_xml)
237
-
238
- process_raw_mapping(element, xml_mapping.raw_mapping, prefixed_xml)
239
237
  end
240
238
  end
241
239
 
242
- def process_raw_mapping(element, rule, xml)
243
- return unless rule
244
-
245
- value = attribute_value_for(element, rule)
246
- return unless render_element?(rule, element, value)
247
-
248
- xml.add_text(xml, value, cdata: rule.cdata)
249
- end
250
-
251
240
  def process_content_mapping(element, content_rule, xml)
252
241
  return unless content_rule
253
242
 
@@ -343,7 +332,7 @@ module Lutaml
343
332
  {}
344
333
  end
345
334
 
346
- if element.respond_to?(:schema_location) && element.schema_location
335
+ if element.respond_to?(:schema_location) && element.schema_location && !options[:except]&.include?(:schema_location)
347
336
  attrs.merge!(element.schema_location.to_xml_attributes)
348
337
  end
349
338
 
@@ -355,10 +344,9 @@ module Lutaml
355
344
  hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
356
345
  end
357
346
 
358
- if render_element?(mapping_rule, element,
359
- mapping_rule.to_value_for(element))
360
- hash[mapping_rule.prefixed_name] =
361
- mapping_rule.to_value_for(element)
347
+ value = mapping_rule.to_value_for(element)
348
+ if render_element?(mapping_rule, element, value)
349
+ hash[mapping_rule.prefixed_name] = value ? value.to_s : value
362
350
  end
363
351
  end
364
352
 
@@ -14,7 +14,7 @@ module Lutaml
14
14
  PERMITTED_CLASSES_BASE
15
15
  end.freeze
16
16
 
17
- def self.parse(yaml)
17
+ def self.parse(yaml, _options = {})
18
18
  YAML.safe_load(yaml, permitted_classes: PERMITTED_CLASSES)
19
19
  end
20
20
 
@@ -7,7 +7,7 @@ module Lutaml
7
7
  module YamlAdapter
8
8
  # Base class for YAML documents
9
9
  class YamlDocument < YamlObject
10
- def self.parse(yaml)
10
+ def self.parse(yaml, _options = {})
11
11
  raise NotImplementedError, "Subclasses must implement `parse`."
12
12
  end
13
13
 
@@ -0,0 +1,5 @@
1
+ <note>
2
+ <to>Jos�</to>
3
+ <from>M�ller</from>
4
+ <heading>Reminder</heading>
5
+ </note>
@@ -0,0 +1,4 @@
1
+ <root>
2
+ <FieldName>�菑���p���P</FieldName>
3
+ <FieldName>123456</FieldName>
4
+ </root>
@@ -138,6 +138,22 @@ RSpec.describe CustomSerialization do
138
138
  end
139
139
  end
140
140
 
141
+ context "with partial JSON input" do
142
+ it "deserializes from JSON with missing attributes" do
143
+ json = {
144
+ name: "JSON Masterpiece: Vase",
145
+ color: "BLUE",
146
+ }.to_json
147
+
148
+ ceramic = described_class.from_json(json)
149
+
150
+ expect(ceramic.full_name).to eq("Vase")
151
+ expect(ceramic.color).to eq("blue")
152
+ expect(ceramic.size).to be_nil
153
+ expect(ceramic.description).to be_nil
154
+ end
155
+ end
156
+
141
157
  context "with XML serialization" do
142
158
  it "serializes to XML with custom methods" do
143
159
  expected_xml = <<~XML
@@ -105,6 +105,29 @@ module DefaultsSpec
105
105
  map "opacity", to: :opacity, render_default: false
106
106
  end
107
107
  end
108
+
109
+ # Class for testing render_default: true with custom model
110
+ class Lang
111
+ attr_accessor :lang, :content
112
+ end
113
+
114
+ class CustomModelWithDefaultValue < Lutaml::Model::Serializable
115
+ model Lang
116
+
117
+ attribute :lang, :string, default: -> { "en" }
118
+ attribute :content, :string, default: -> { "default value not render when render_default is false" }
119
+
120
+ xml do
121
+ root "CustomModelWithDefaultValue"
122
+ map_attribute "lang", to: :lang, render_default: true
123
+ map_content to: :content, render_default: false
124
+ end
125
+
126
+ key_value do
127
+ map "lang", to: :lang, render_default: true
128
+ map "content", to: :content, render_default: false
129
+ end
130
+ end
108
131
  end
109
132
 
110
133
  RSpec.describe DefaultsSpec::Glaze do
@@ -125,6 +148,13 @@ RSpec.describe DefaultsSpec::Glaze do
125
148
  expect(default_model.manufacturer).to eq("example@glazes.com")
126
149
  expect(default_model.type).to eq("stoneware")
127
150
  end
151
+
152
+ it "contains the default value in instance" do
153
+ instance = DefaultsSpec::CustomModelWithDefaultValue.new
154
+
155
+ expect(instance.content).to eq("default value not render when render_default is false")
156
+ expect(instance.lang).to eq("en")
157
+ end
128
158
  end
129
159
 
130
160
  describe "Default value rendering behavior" do
@@ -155,6 +185,15 @@ RSpec.describe DefaultsSpec::Glaze do
155
185
  expect(xml).to include("<Opacity>Opaque</Opacity>")
156
186
  expect(xml).to include("<FiringTime>60</FiringTime>")
157
187
  end
188
+
189
+ it "serializes when render_default is true with custom model" do
190
+ parsed = DefaultsSpec::CustomModelWithDefaultValue.from_xml "<CustomModelWithDefaultValue>English</CustomModelWithDefaultValue>"
191
+ expect(parsed.lang).to eq("en")
192
+ expect(parsed.content).to eq("English")
193
+
194
+ serialized = DefaultsSpec::CustomModelWithDefaultValue.to_xml(parsed)
195
+ expect(serialized).to eq("<CustomModelWithDefaultValue lang=\"en\">English</CustomModelWithDefaultValue>")
196
+ end
158
197
  end
159
198
 
160
199
  context "when value is not default" do
@@ -200,6 +239,15 @@ RSpec.describe DefaultsSpec::Glaze do
200
239
  expect(json["opacity"]).to eq("Opaque")
201
240
  expect(json["firingTime"]).to eq(60)
202
241
  end
242
+
243
+ it "serializes when render_default is true with custom model" do
244
+ parsed = DefaultsSpec::CustomModelWithDefaultValue.from_json('{"content": "content"}')
245
+ expect(parsed.lang).to eq("en")
246
+ expect(parsed.content).to eq("content")
247
+
248
+ serialized = DefaultsSpec::CustomModelWithDefaultValue.to_json(parsed)
249
+ expect(serialized).to eq('{"lang":"en","content":"content"}')
250
+ end
203
251
  end
204
252
 
205
253
  context "when value is not default" do
@@ -217,5 +265,31 @@ RSpec.describe DefaultsSpec::Glaze do
217
265
  end
218
266
  end
219
267
  end
268
+
269
+ context "with YAML serialization" do
270
+ context "when value is default" do
271
+ it "serializes when render_default is true with custom model" do
272
+ parsed = DefaultsSpec::CustomModelWithDefaultValue.from_yaml("---\ncontent: content")
273
+ expect(parsed.lang).to eq("en")
274
+ expect(parsed.content).to eq("content")
275
+
276
+ serialized = DefaultsSpec::CustomModelWithDefaultValue.to_yaml(parsed)
277
+ expect(serialized).to eq("---\nlang: en\ncontent: content\n")
278
+ end
279
+ end
280
+ end
281
+
282
+ context "with TOML serialization" do
283
+ context "when value is default" do
284
+ it "serializes when render_default is true with custom model" do
285
+ parsed = DefaultsSpec::CustomModelWithDefaultValue.from_toml("content = 'content'")
286
+ expect(parsed.lang).to eq("en")
287
+ expect(parsed.content).to eq("content")
288
+
289
+ serialized = DefaultsSpec::CustomModelWithDefaultValue.to_toml(parsed)
290
+ expect(serialized).to eq("content = \"content\"\nlang = \"en\"\n")
291
+ end
292
+ end
293
+ end
220
294
  end
221
295
  end
@@ -0,0 +1,131 @@
1
+ require "spec_helper"
2
+ require "lutaml/model"
3
+
4
+ module EnumSpec
5
+ class WithEnum < Lutaml::Model::Serializable
6
+ attribute :without_enum, :string
7
+ attribute :single_value, :string, values: %w[user admin super_admin]
8
+ attribute :multi_value, :string, values: %w[singular dual plural], collection: true
9
+ end
10
+ end
11
+
12
+ RSpec.describe "Enum" do
13
+ let(:single_value_attr) do
14
+ EnumSpec::WithEnum.attributes[:single_value]
15
+ end
16
+
17
+ let(:multi_value_attr) do
18
+ EnumSpec::WithEnum.attributes[:multi_value]
19
+ end
20
+
21
+ let(:without_enum_attr) do
22
+ EnumSpec::WithEnum.attributes[:without_enum]
23
+ end
24
+
25
+ let(:object) do
26
+ EnumSpec::WithEnum.new
27
+ end
28
+
29
+ context "when values are provided for an attribute" do
30
+ it "is marked as enum for single_value" do
31
+ expect(single_value_attr.enum?).to be(true)
32
+ end
33
+
34
+ it "is marked as enum for multi_value" do
35
+ expect(multi_value_attr.enum?).to be(true)
36
+ end
37
+
38
+ context "with enum convinience methods" do
39
+ describe "#single_value" do
40
+ it "returns single value" do
41
+ expect { object.single_value = "user" }
42
+ .to change(object, :single_value)
43
+ .from(nil)
44
+ .to("user")
45
+ end
46
+ end
47
+
48
+ describe "#multi_value" do
49
+ it "returns single value in array" do
50
+ expect { object.multi_value = "dual" }
51
+ .to change(object, :multi_value)
52
+ .from([])
53
+ .to(["dual"])
54
+ end
55
+
56
+ it "returns multiple value in array" do
57
+ expect { object.multi_value = %w[dual plural] }
58
+ .to change(object, :multi_value)
59
+ .from([])
60
+ .to(%w[dual plural])
61
+ end
62
+ end
63
+
64
+ EnumSpec::WithEnum.enums.each_value do |enum_attr|
65
+ enum_attr.enum_values.each do |value|
66
+ describe "##{value}=" do
67
+ it "sets the #{value} if true" do
68
+ expect { object.public_send(:"#{value}=", true) }
69
+ .to change { object.public_send(:"#{value}?") }
70
+ .from(false)
71
+ .to(true)
72
+ end
73
+
74
+ it "unsets the #{value} if false" do
75
+ object.public_send(:"#{value}=", true)
76
+
77
+ expect { object.public_send(:"#{value}=", false) }
78
+ .to change(object, "#{value}?")
79
+ .from(true)
80
+ .to(false)
81
+ end
82
+ end
83
+
84
+ describe "##{value}!" do
85
+ it "method #{value}? should be present" do
86
+ expect(object.respond_to?(:"#{value}!")).to be(true)
87
+ end
88
+
89
+ it "sets #{value} to true for enum" do
90
+ expect { object.public_send(:"#{value}!") }
91
+ .to change(object, "#{value}?")
92
+ .from(false)
93
+ .to(true)
94
+ end
95
+ end
96
+
97
+ describe "##{value}?" do
98
+ it "method #{value}? should be present" do
99
+ expect(object.respond_to?(:"#{value}?")).to be(true)
100
+ end
101
+
102
+ it "is false if role is not #{value}" do
103
+ expect(object.public_send(:"#{value}?")).to be(false)
104
+ end
105
+
106
+ it "is true if role is set to #{value}" do
107
+ expect { object.public_send(:"#{value}=", value) }
108
+ .to change(object, "#{value}?")
109
+ .from(false)
110
+ .to(true)
111
+ end
112
+ end
113
+
114
+ it "adds a method named #{value}=" do
115
+ expect(object.respond_to?(:"#{value}?")).to be(true)
116
+ end
117
+
118
+ it "adds a method named #{value}!" do
119
+ expect(object.respond_to?(:"#{value}!")).to be(true)
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ context "when values are not provided for an attribute" do
127
+ it "is not marked as enum" do
128
+ expect(without_enum_attr.enum?).to be(false)
129
+ end
130
+ end
131
+ end
@@ -72,6 +72,28 @@ module MixedContentSpec
72
72
  end
73
73
  end
74
74
 
75
+ class Latin < Lutaml::Model::Serializable
76
+ attribute :the, :string
77
+ attribute :from, :string
78
+ attribute :heading, :string
79
+
80
+ xml do
81
+ root "note"
82
+ map_element "to", to: :the
83
+ map_element "from", to: :from
84
+ map_element "heading", to: :heading
85
+ end
86
+ end
87
+
88
+ class Shift < Lutaml::Model::Serializable
89
+ attribute :field, :string, collection: true
90
+
91
+ xml do
92
+ root "root"
93
+ map_element "FieldName", to: :field
94
+ end
95
+ end
96
+
75
97
  class SpecialCharContentWithMixedTrue < Lutaml::Model::Serializable
76
98
  attribute :content, :string
77
99
 
@@ -651,24 +673,185 @@ RSpec.describe "MixedContent" do
651
673
 
652
674
  context "when encoding: nil xml" do
653
675
  let(:expected_encoding_nil_nokogiri_xml) { "&#x2211;computer security&#x220F; type of &#x200B; operation specified &#xB5; by an access right" }
654
- let(:expected_encoding_nil_ox_xml) { "\xE2\x88\x91computer security\xE2\x88\x8F type of \xE2\x80\x8B operation specified \xC2\xB5 by an access right" }
676
+ let(:expected_encoding_nil_ox_xml) { "∑computer security type of operation specified µ by an access right" }
655
677
 
656
678
  it "serializes special char mixed content correctly with encoding: nil to get hexcode" do
657
679
  parsed = MixedContentSpec::HexCode.from_xml(xml)
658
680
  serialized = parsed.to_xml(encoding: nil)
659
681
 
660
- if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
661
- expected_output = expected_encoding_nil_ox_xml
662
- expected_output.force_encoding("ASCII-8BIT")
663
- else
664
- expected_output = expected_encoding_nil_nokogiri_xml
665
- end
682
+ expected_output = if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
683
+ expected_encoding_nil_ox_xml
684
+ else
685
+ expected_encoding_nil_nokogiri_xml
686
+ end
666
687
 
667
688
  expect(serialized.strip).to include(expected_output)
668
689
  end
669
690
  end
670
691
  end
671
692
  end
693
+
694
+ context "when use encoding in parsing" do
695
+ context "when use SHIFT-JIS encoding" do
696
+ let(:fixture) { File.read(fixture_path("xml/shift_jis.xml"), encoding: "Shift_JIS") }
697
+
698
+ describe ".from_xml" do
699
+ it "verifies the encoding of file read" do
700
+ expect(fixture.encoding.to_s).to eq("Shift_JIS")
701
+ end
702
+
703
+ it "deserializes SHIFT encoded content correctly with explicit encoding option" do
704
+ parsed = MixedContentSpec::Shift.from_xml(fixture, encoding: "Shift_JIS")
705
+
706
+ expected_content = if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
707
+ "\x8E\xE8\x8F\x91\x82\xAB\x89p\x8E\x9A\x82P".force_encoding("Shift_JIS")
708
+ else
709
+ "手書き英字1"
710
+ end
711
+
712
+ expect(parsed.field).to include(expected_content)
713
+ end
714
+
715
+ it "deserializes SHIFT encoded content incorrectly without explicit encoding option" do
716
+ parsed = MixedContentSpec::Shift.from_xml(fixture)
717
+
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
721
+ "�菑���p���P"
722
+ end
723
+
724
+ expect(parsed.field).to include(expected_content)
725
+ end
726
+ end
727
+
728
+ describe ".to_xml" do
729
+ it "serializes SHIFT-JIS encoding content correctly reading from file" do
730
+ parsed = MixedContentSpec::Shift.from_xml(fixture, encoding: "Shift_JIS")
731
+ serialized = parsed.to_xml
732
+
733
+ expect(serialized.strip).to eq(fixture.strip)
734
+ end
735
+
736
+ it "serializes SHIFT encoded content correctly with explicit encoding option both in parsing and deserializing" do
737
+ parsed = MixedContentSpec::Shift.from_xml(fixture, encoding: "Shift_JIS")
738
+ serialized = parsed.to_xml(encoding: "UTF-8")
739
+
740
+ expected_xml = if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
741
+ "\x8E\xE8\x8F\x91\x82\xAB\x89p\x8E\x9A\x82P".force_encoding("Shift_JIS")
742
+ else
743
+ "手書き英字1"
744
+ end
745
+
746
+ expect(parsed.field).to include(expected_xml)
747
+ expect(parsed.encoding).to eq("Shift_JIS")
748
+
749
+ expect(serialized).to include("手書き英字1")
750
+ expect(serialized.encoding.to_s).to eq("UTF-8")
751
+ end
752
+
753
+ it "serializes SHIFT encoded content correctly with explicit encoding option" do
754
+ parsed = MixedContentSpec::Shift.from_xml(fixture, encoding: "Shift_JIS")
755
+ serialized = parsed.to_xml(encoding: "Shift_JIS")
756
+
757
+ expected_xml = if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
758
+ "\x8E\xE8\x8F\x91\x82\xAB\x89p\x8E\x9A\x82P".force_encoding("Shift_JIS")
759
+ else
760
+ "手書き英字1"
761
+ end
762
+
763
+ expect(parsed.field).to include(expected_xml)
764
+ expect(parsed.encoding).to eq("Shift_JIS")
765
+
766
+ expect(serialized).to include("\x8E\xE8\x8F\x91\x82\xAB\x89p\x8E\x9A\x82P".force_encoding("Shift_JIS"))
767
+ expect(serialized.encoding.to_s).to eq("Shift_JIS")
768
+ end
769
+
770
+ it "serializes SHIFT encoded content correctly with declaration: true" do
771
+ parsed = MixedContentSpec::Shift.from_xml(fixture, encoding: "Shift_JIS")
772
+ serialized = parsed.to_xml(declaration: true, encoding: "Shift_JIS")
773
+
774
+ expected_xml = "<?xml version=\"1.0\" encoding=\"Shift_JIS\"?>\n<root>\n <FieldName>\x8E\xE8\x8F\x91\x82\xAB\x89p\x8E\x9A\x82P</FieldName>\n <FieldName>123456</FieldName>\n</root>"
775
+
776
+ expect(serialized).to be_equivalent_to(expected_xml)
777
+ expect(serialized.encoding.to_s).to eq("Shift_JIS")
778
+ end
779
+
780
+ it "serializes SHIFT-JIS content incorrectly bcz no encoding provided during parsing" do
781
+ parsed = MixedContentSpec::Shift.from_xml(fixture)
782
+ serialized = parsed.to_xml(encoding: "Shift_JIS")
783
+
784
+ expected_content = if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
785
+ "<root>\n <FieldName>\x8E菑\x82\xAB\x89p\x8E\x9A\x82P</FieldName>\n <FieldName>123456</FieldName>\n</root>\n"
786
+ else
787
+ "<root>\n <FieldName>&#65533;&#33745;&#65533;&#65533;&#65533;p&#65533;&#65533;&#65533;P</FieldName>\n <FieldName>123456</FieldName>\n</root>"
788
+ end
789
+
790
+ expect(serialized).to eq(expected_content)
791
+ end
792
+
793
+ it "serializes SHIFT-JIS encoding content correctly reading from string" do
794
+ xml = "<root><FieldName>手書き英字1</FieldName><FieldName>123456</FieldName></root>".encode("Shift_JIS")
795
+ parsed = MixedContentSpec::Shift.from_xml(xml, encoding: "Shift_JIS")
796
+ serialized = parsed.to_xml(encoding: "Shift_JIS")
797
+
798
+ expect(serialized).to be_equivalent_to(xml)
799
+ end
800
+
801
+ it "serializes SHIFT-JIS encoding content correctly" do
802
+ parsed = MixedContentSpec::Shift.from_xml(fixture, encoding: "Shift_JIS")
803
+ serialized = parsed.to_xml(encoding: "Shift_JIS")
804
+
805
+ expect(serialized).to be_equivalent_to(fixture)
806
+ end
807
+ end
808
+ end
809
+
810
+ context "when use LATIN (ISO-8859-1) encoding" do
811
+ let(:fixture) { File.read(fixture_path("xml/latin_encoding.xml"), encoding: "ISO-8859-1") }
812
+
813
+ describe ".from_xml" do
814
+ it "verifies the encoding of file read" do
815
+ expect(fixture.encoding.to_s).to eq("ISO-8859-1")
816
+ end
817
+
818
+ it "deserializes latin encoded content correctly" do
819
+ parsed = MixedContentSpec::Latin.from_xml(fixture, encoding: "ISO-8859-1")
820
+
821
+ expected_content = if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
822
+ ["M\xFCller".force_encoding("ISO-8859-1"), "Jos\xE9".force_encoding("ISO-8859-1")]
823
+ else
824
+ ["Müller", "José"]
825
+ end
826
+
827
+ expect(parsed.from).to eq(expected_content[0])
828
+ expect(parsed.the).to eq(expected_content[1])
829
+ end
830
+
831
+ it "deserializes latin encoded content incorrectly" do
832
+ parsed = MixedContentSpec::Latin.from_xml(fixture)
833
+
834
+ expected_content = if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
835
+ ["M\xFCller", "Jos\xE9"]
836
+ else
837
+ ["M�ller", "Jos�"]
838
+ end
839
+
840
+ expect(parsed.from).to eq(expected_content[0])
841
+ expect(parsed.the).to eq(expected_content[1])
842
+ end
843
+ end
844
+
845
+ describe ".to_xml" do
846
+ it "serializes latin encoded content correctly" do
847
+ parsed = MixedContentSpec::Latin.from_xml(fixture, encoding: "ISO-8859-1")
848
+ 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"))
851
+ end
852
+ end
853
+ end
854
+ end
672
855
  end
673
856
 
674
857
  describe Lutaml::Model::XmlAdapter::NokogiriAdapter do
@@ -68,7 +68,6 @@ RSpec.describe RenderNil do
68
68
  name: nil,
69
69
  clay_type: nil,
70
70
  glaze: nil,
71
- dimensions: [],
72
71
  }.to_json
73
72
 
74
73
  expect(model.to_json).to eq(expected_json)
@@ -113,7 +112,6 @@ RSpec.describe RenderNil do
113
112
  ---
114
113
  name:
115
114
  glaze:
116
- dimensions: []
117
115
  YAML
118
116
 
119
117
  generated_yaml = model.to_yaml.strip