lutaml-model 0.3.29 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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