lutaml-model 0.3.24 → 0.3.25

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +35 -16
  3. data/README.adoc +274 -28
  4. data/lib/lutaml/model/attribute.rb +18 -8
  5. data/lib/lutaml/model/error/type_error.rb +9 -0
  6. data/lib/lutaml/model/error/unknown_type_error.rb +9 -0
  7. data/lib/lutaml/model/error/validation_error.rb +0 -1
  8. data/lib/lutaml/model/error.rb +2 -0
  9. data/lib/lutaml/model/serialize.rb +6 -1
  10. data/lib/lutaml/model/type/boolean.rb +38 -0
  11. data/lib/lutaml/model/type/date.rb +35 -0
  12. data/lib/lutaml/model/type/date_time.rb +32 -4
  13. data/lib/lutaml/model/type/decimal.rb +42 -0
  14. data/lib/lutaml/model/type/float.rb +37 -0
  15. data/lib/lutaml/model/type/hash.rb +62 -0
  16. data/lib/lutaml/model/type/integer.rb +41 -0
  17. data/lib/lutaml/model/type/string.rb +49 -0
  18. data/lib/lutaml/model/type/time.rb +49 -0
  19. data/lib/lutaml/model/type/time_without_date.rb +37 -5
  20. data/lib/lutaml/model/type/value.rb +52 -0
  21. data/lib/lutaml/model/type.rb +50 -114
  22. data/lib/lutaml/model/version.rb +1 -1
  23. data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +5 -2
  24. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +2 -1
  25. data/lib/lutaml/model/xml_adapter/xml_document.rb +0 -2
  26. data/lutaml-model.gemspec +1 -1
  27. data/spec/address_spec.rb +170 -0
  28. data/spec/fixtures/address.rb +33 -0
  29. data/spec/fixtures/person.rb +73 -0
  30. data/spec/fixtures/sample_model.rb +40 -0
  31. data/spec/fixtures/vase.rb +38 -0
  32. data/spec/fixtures/xml/special_char.xml +13 -0
  33. data/spec/lutaml/model/attribute_spec.rb +112 -0
  34. data/spec/lutaml/model/collection_spec.rb +299 -0
  35. data/spec/lutaml/model/comparable_model_spec.rb +106 -0
  36. data/spec/lutaml/model/custom_model_spec.rb +410 -0
  37. data/spec/lutaml/model/custom_serialization_spec.rb +170 -0
  38. data/spec/lutaml/model/defaults_spec.rb +221 -0
  39. data/spec/lutaml/model/delegation_spec.rb +340 -0
  40. data/spec/lutaml/model/inheritance_spec.rb +92 -0
  41. data/spec/lutaml/model/json_adapter_spec.rb +37 -0
  42. data/spec/lutaml/model/key_value_mapping_spec.rb +86 -0
  43. data/spec/lutaml/model/map_content_spec.rb +118 -0
  44. data/spec/lutaml/model/mixed_content_spec.rb +625 -0
  45. data/spec/lutaml/model/namespace_spec.rb +57 -0
  46. data/spec/lutaml/model/ordered_content_spec.rb +83 -0
  47. data/spec/lutaml/model/render_nil_spec.rb +138 -0
  48. data/spec/lutaml/model/schema/json_schema_spec.rb +79 -0
  49. data/spec/lutaml/model/schema/relaxng_schema_spec.rb +60 -0
  50. data/spec/lutaml/model/schema/xsd_schema_spec.rb +55 -0
  51. data/spec/lutaml/model/schema/yaml_schema_spec.rb +47 -0
  52. data/spec/lutaml/model/serializable_spec.rb +297 -0
  53. data/spec/lutaml/model/serializable_validation_spec.rb +85 -0
  54. data/spec/lutaml/model/simple_model_spec.rb +314 -0
  55. data/spec/lutaml/model/toml_adapter_spec.rb +39 -0
  56. data/spec/lutaml/model/type/boolean_spec.rb +54 -0
  57. data/spec/lutaml/model/type/date_spec.rb +118 -0
  58. data/spec/lutaml/model/type/date_time_spec.rb +127 -0
  59. data/spec/lutaml/model/type/decimal_spec.rb +125 -0
  60. data/spec/lutaml/model/type/float_spec.rb +191 -0
  61. data/spec/lutaml/model/type/hash_spec.rb +63 -0
  62. data/spec/lutaml/model/type/integer_spec.rb +145 -0
  63. data/spec/lutaml/model/type/string_spec.rb +150 -0
  64. data/spec/lutaml/model/type/time_spec.rb +142 -0
  65. data/spec/lutaml/model/type/time_without_date_spec.rb +125 -0
  66. data/spec/lutaml/model/type_spec.rb +276 -0
  67. data/spec/lutaml/model/utils_spec.rb +79 -0
  68. data/spec/lutaml/model/validation_spec.rb +83 -0
  69. data/spec/lutaml/model/with_child_mapping_spec.rb +174 -0
  70. data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +56 -0
  71. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +56 -0
  72. data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +61 -0
  73. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +251 -0
  74. data/spec/lutaml/model/xml_adapter_spec.rb +178 -0
  75. data/spec/lutaml/model/xml_mapping_spec.rb +863 -0
  76. data/spec/lutaml/model/yaml_adapter_spec.rb +30 -0
  77. data/spec/lutaml/model_spec.rb +1 -0
  78. data/spec/person_spec.rb +161 -0
  79. data/spec/spec_helper.rb +33 -0
  80. metadata +66 -2
@@ -0,0 +1,625 @@
1
+ # spec/lutaml/model/mixed_content_spec.rb
2
+
3
+ require "spec_helper"
4
+ require "lutaml/model"
5
+ require "lutaml/model/xml_adapter/nokogiri_adapter"
6
+ require "lutaml/model/xml_adapter/ox_adapter"
7
+ require "lutaml/model/xml_adapter/oga_adapter"
8
+ require_relative "../../fixtures/sample_model"
9
+
10
+ module MixedContentSpec
11
+ class PlanetaryBody < Lutaml::Model::Serializable
12
+ attribute :name, :string
13
+ attribute :distance_from_earth, :integer
14
+ xml do
15
+ root "PlanetaryBody"
16
+ map_element "Name", to: :name
17
+ map_element "DistanceFromEarth", to: :distance_from_earth
18
+ end
19
+ end
20
+
21
+ class Source < Lutaml::Model::Serializable
22
+ attribute :content, :string
23
+
24
+ xml do
25
+ root "source"
26
+ map_content to: :content
27
+ end
28
+ end
29
+
30
+ class ElementCitation < Lutaml::Model::Serializable
31
+ attribute :source, Source
32
+
33
+ xml do
34
+ root "element-citation"
35
+ map_element "source", to: :source
36
+ end
37
+ end
38
+
39
+ class Ref < Lutaml::Model::Serializable
40
+ attribute :element_citation, ElementCitation
41
+
42
+ xml do
43
+ root "ref"
44
+ map_element "element-citation", to: :element_citation
45
+ end
46
+ end
47
+
48
+ class RefList < Lutaml::Model::Serializable
49
+ attribute :ref, Ref
50
+
51
+ xml do
52
+ root "ref-list"
53
+ map_element "ref", to: :ref
54
+ end
55
+ end
56
+
57
+ class Back < Lutaml::Model::Serializable
58
+ attribute :ref_list, RefList
59
+
60
+ xml do
61
+ root "back"
62
+ map_element "ref-list", to: :ref_list
63
+ end
64
+ end
65
+
66
+ class Article < Lutaml::Model::Serializable
67
+ attribute :back, Back
68
+
69
+ xml do
70
+ root "article"
71
+ map_element "back", to: :back
72
+ end
73
+ end
74
+
75
+ class SpecialCharContentWithMixedTrue < Lutaml::Model::Serializable
76
+ attribute :content, :string
77
+
78
+ xml do
79
+ root "SpecialCharContentWithMixedTrue", mixed: true
80
+ map_content to: :content
81
+ end
82
+ end
83
+
84
+ class SpecialCharContentWithRawAndMixedOption < Lutaml::Model::Serializable
85
+ attribute :special, :string, raw: true
86
+
87
+ xml do
88
+ root "SpecialCharContentWithRawOptionAndMixedOption", mixed: true
89
+ map_element :special, to: :special
90
+ end
91
+ end
92
+
93
+ class RootMixedContent < Lutaml::Model::Serializable
94
+ attribute :id, :string
95
+ attribute :bold, :string, collection: true
96
+ attribute :italic, :string, collection: true
97
+ attribute :underline, :string
98
+ attribute :content, :string
99
+
100
+ xml do
101
+ root "RootMixedContent", mixed: true
102
+ map_attribute :id, to: :id
103
+ map_element :bold, to: :bold
104
+ map_element :italic, to: :italic
105
+ map_element :underline, to: :underline
106
+ map_content to: :content
107
+ end
108
+ end
109
+
110
+ class RootMixedContentWithModel < Lutaml::Model::Serializable
111
+ attribute :id, :string
112
+ attribute :bold, :string, collection: true
113
+ attribute :italic, :string, collection: true
114
+ attribute :underline, :string
115
+ attribute :planetary_body, PlanetaryBody
116
+ attribute :content, :string
117
+
118
+ xml do
119
+ root "RootMixedContentWithModel", mixed: true
120
+ map_content to: :content
121
+ map_attribute :id, to: :id
122
+ map_element :bold, to: :bold
123
+ map_element :italic, to: :italic
124
+ map_element :underline, to: :underline
125
+ map_element "PlanetaryBody", to: :planetary_body
126
+ end
127
+ end
128
+
129
+ class RootMixedContentNested < Lutaml::Model::Serializable
130
+ attribute :id, :string
131
+ attribute :text, :string
132
+ attribute :content, RootMixedContent
133
+ attribute :sup, :string, collection: true
134
+ attribute :sub, :string, collection: true
135
+
136
+ xml do
137
+ root "RootMixedContentNested", mixed: true
138
+ map_content to: :text
139
+ map_attribute :id, to: :id
140
+ map_element :sup, to: :sup
141
+ map_element :sub, to: :sub
142
+ map_element "MixedContent", to: :content
143
+ end
144
+ end
145
+
146
+ class RootMixedContentNestedWithModel < Lutaml::Model::Serializable
147
+ attribute :id, :string
148
+ attribute :text, :string
149
+ attribute :content, RootMixedContentWithModel
150
+ attribute :sup, :string, collection: true
151
+ attribute :sub, :string, collection: true
152
+
153
+ xml do
154
+ root "RootMixedContentNestedWithModel", mixed: true
155
+
156
+ map_content to: :text
157
+ map_attribute :id, to: :id
158
+ map_element :sup, to: :sup
159
+ map_element :sub, to: :sub
160
+ map_element "MixedContentWithModel", to: :content
161
+ end
162
+ end
163
+
164
+ class TextualSupport < Lutaml::Model::Serializable
165
+ attribute :value, :string
166
+
167
+ xml do
168
+ root "TextualSupport"
169
+
170
+ map_element :value, to: :value
171
+ end
172
+ end
173
+ end
174
+
175
+ RSpec.describe "MixedContent" do
176
+ shared_examples "mixed content behavior" do |adapter_class|
177
+ around do |example|
178
+ old_adapter = Lutaml::Model::Config.xml_adapter
179
+ Lutaml::Model::Config.xml_adapter = adapter_class
180
+ example.run
181
+ ensure
182
+ Lutaml::Model::Config.xml_adapter = old_adapter
183
+ end
184
+
185
+ context "when mixed: true is set at root" do
186
+ let(:xml) do
187
+ <<~XML
188
+ <RootMixedContent id="123">
189
+ The Earth's Moon rings like a <bold>bell</bold> when struck by
190
+ meteroids. Distanced from the Earth by <italic>384,400 km</italic>,
191
+ its surface is covered in <underline>craters</underline>.
192
+ Ain't that <bold>cool</bold>?
193
+ </RootMixedContent>
194
+ XML
195
+ end
196
+
197
+ it "deserializes and serializes mixed content correctly" do
198
+ parsed = MixedContentSpec::RootMixedContent.from_xml(xml)
199
+
200
+ expected_content = [
201
+ "\n The Earth's Moon rings like a ",
202
+ " when struck by\n meteroids. Distanced from the Earth by ",
203
+ ",\n its surface is covered in ",
204
+ ".\n Ain't that ",
205
+ "?\n",
206
+ ]
207
+
208
+ expect(parsed.id).to eq("123")
209
+ expect(parsed.bold).to eq(["bell", "cool"])
210
+ expect(parsed.italic).to eq(["384,400 km"])
211
+ expect(parsed.underline).to eq("craters")
212
+
213
+ parsed.content.each_with_index do |content, index|
214
+ expected_output = expected_content[index]
215
+
216
+ # due to the difference in capturing
217
+ # newlines in ox and nokogiri adapters
218
+ if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
219
+ expected_output = expected_output.gsub(/\n\s*/, " ")
220
+ end
221
+
222
+ expect(content).to eq(expected_output)
223
+ end
224
+
225
+ serialized = parsed.to_xml
226
+ expect(serialized).to be_equivalent_to(xml)
227
+ end
228
+ end
229
+
230
+ context "when mixed: true is set at root with nested model" do
231
+ let(:xml) do
232
+ <<~XML
233
+ <RootMixedContentWithModel id="123">
234
+ The Earth's Moon rings like a <bold>bell</bold> when struck by
235
+ meteroids. Distanced from the Earth by <italic>384,400 km</italic>,
236
+ its surface is covered in <underline>craters</underline>.
237
+ Ain't that <bold>cool</bold>?
238
+ <PlanetaryBody>
239
+ <Name>Moon</Name>
240
+ <DistanceFromEarth>384400</DistanceFromEarth>
241
+ </PlanetaryBody>
242
+ NOTE: The above model content is to be formatted as a table.
243
+ </RootMixedContentWithModel>
244
+ XML
245
+ end
246
+
247
+ it "deserializes and serializes mixed content correctly" do
248
+ parsed = MixedContentSpec::RootMixedContentWithModel.from_xml(xml)
249
+
250
+ expected_content = [
251
+ "\n The Earth's Moon rings like a ",
252
+ " when struck by\n meteroids. Distanced from the Earth by ",
253
+ ",\n its surface is covered in ",
254
+ ".\n Ain't that ",
255
+ "?\n ",
256
+ "\n NOTE: The above model content is to be formatted as a table.\n",
257
+ ]
258
+
259
+ expect(parsed.id).to eq("123")
260
+ expect(parsed.bold).to eq(["bell", "cool"])
261
+ expect(parsed.italic).to eq(["384,400 km"])
262
+ expect(parsed.underline).to eq("craters")
263
+
264
+ parsed.content.each_with_index do |content, index|
265
+ expected_output = expected_content[index]
266
+
267
+ # due to the difference in capturing
268
+ # newlines in ox and nokogiri adapters
269
+ if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
270
+ expected_output = expected_output.gsub(/\n\s*/, " ")
271
+ end
272
+
273
+ expect(content).to eq(expected_output)
274
+ end
275
+
276
+ expect(parsed.planetary_body.name).to eq("Moon")
277
+ expect(parsed.planetary_body.distance_from_earth).to eq(384400)
278
+
279
+ serialized = parsed.to_xml
280
+ expect(serialized).to be_equivalent_to(xml)
281
+ end
282
+ end
283
+
284
+ context "when mixed: true is set for nested content" do
285
+ let(:xml) do
286
+ <<~XML
287
+ <RootMixedContentNested id="outer123">
288
+ The following text is about the Moon.
289
+ <MixedContent id="inner456">
290
+ The Earth's Moon rings like a <bold>bell</bold> when struck by
291
+ meteroids. Distanced from the Earth by <italic>384,400 km</italic>,
292
+ its surface is covered in <underline>craters</underline>.
293
+ Ain't that <bold>cool</bold>?
294
+ </MixedContent>
295
+ <sup>1</sup>: The Moon is not a planet.
296
+ <sup>2</sup>: The Moon's atmosphere is mainly composed of helium in the form of He<sub>2</sub>.
297
+ </RootMixedContentNested>
298
+ XML
299
+ end
300
+
301
+ it "deserializes and serializes mixed content correctly" do
302
+ parsed = MixedContentSpec::RootMixedContentNested.from_xml(xml)
303
+
304
+ expected_content = [
305
+ "\n The Earth's Moon rings like a ",
306
+ " when struck by\n meteroids. Distanced from the Earth by ",
307
+ ",\n its surface is covered in ",
308
+ ".\n Ain't that ",
309
+ "?\n ",
310
+ ]
311
+
312
+ expect(parsed.id).to eq("outer123")
313
+ expect(parsed.sup).to eq(["1", "2"])
314
+ expect(parsed.sub).to eq(["2"])
315
+ expect(parsed.content.id).to eq("inner456")
316
+ expect(parsed.content.bold).to eq(["bell", "cool"])
317
+ expect(parsed.content.italic).to eq(["384,400 km"])
318
+ expect(parsed.content.underline).to eq("craters")
319
+
320
+ parsed.content.content.each_with_index do |content, index|
321
+ expected_output = expected_content[index]
322
+
323
+ # due to the difference in capturing
324
+ # newlines in ox and nokogiri adapters
325
+ if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
326
+ expected_output = expected_output.gsub(/\n\s*/, " ")
327
+ end
328
+
329
+ expect(content).to eq(expected_output)
330
+ end
331
+
332
+ serialized = parsed.to_xml
333
+ expect(serialized).to be_equivalent_to(xml)
334
+ end
335
+ end
336
+
337
+ context "when mixed: true is set for nested content with model" do
338
+ let(:xml) do
339
+ <<~XML
340
+ <RootMixedContentNestedWithModel id="outer123">
341
+ The following text is about the Moon.
342
+ <MixedContentWithModel id="inner456">
343
+ The Earth's Moon rings like a <bold>bell</bold> when struck by
344
+ meteroids. Distanced from the Earth by <italic>384,400 km</italic>,
345
+ its surface is covered in <underline>craters</underline>.
346
+ Ain't that <bold>cool</bold>?
347
+ <PlanetaryBody>
348
+ <Name>Moon</Name>
349
+ <DistanceFromEarth>384400</DistanceFromEarth>
350
+ </PlanetaryBody>
351
+ NOTE: The above model content is to be formatted as a table.
352
+ </MixedContentWithModel>
353
+ <sup>1</sup>: The Moon is not a planet.
354
+ <sup>2</sup>: The Moon's atmosphere is mainly composed of helium in the form of He<sub>2</sub>.
355
+ </RootMixedContentNestedWithModel>
356
+ XML
357
+ end
358
+
359
+ it "deserializes and serializes mixed content correctly" do
360
+ parsed = MixedContentSpec::RootMixedContentNestedWithModel.from_xml(xml)
361
+
362
+ expected_content = [
363
+ "\n The Earth's Moon rings like a ",
364
+ " when struck by\n meteroids. Distanced from the Earth by ",
365
+ ",\n its surface is covered in ",
366
+ ".\n Ain't that ",
367
+ "?\n ",
368
+ "\n NOTE: The above model content is to be formatted as a table.\n ",
369
+ ]
370
+
371
+ expect(parsed.id).to eq("outer123")
372
+ expect(parsed.sup).to eq(["1", "2"])
373
+ expect(parsed.sub).to eq(["2"])
374
+ expect(parsed.content.id).to eq("inner456")
375
+ expect(parsed.content.bold).to eq(["bell", "cool"])
376
+ expect(parsed.content.italic).to eq(["384,400 km"])
377
+ expect(parsed.content.underline).to eq("craters")
378
+
379
+ parsed.content.content.each_with_index do |content, index|
380
+ expected_output = expected_content[index]
381
+
382
+ # due to the difference in capturing
383
+ # newlines in ox and nokogiri adapters
384
+ if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
385
+ expected_output = expected_output.gsub(/\n\s*/, " ")
386
+ end
387
+
388
+ expect(content).to eq(expected_output)
389
+ end
390
+
391
+ expect(parsed.content.planetary_body.name).to eq("Moon")
392
+ expect(parsed.content.planetary_body.distance_from_earth).to eq(384400)
393
+
394
+ serialized = parsed.to_xml
395
+ expect(serialized).to be_equivalent_to(xml)
396
+ end
397
+ end
398
+
399
+ context "when mixed: true is used with map_element" do
400
+ it "raises an error" do
401
+ expect do
402
+ Class.new(Lutaml::Model::Serializable) do
403
+ attribute :id, :string
404
+
405
+ xml do
406
+ root "Invalid"
407
+ map_element :id, to: :id, mixed: true
408
+ end
409
+ end
410
+ end.to raise_error(ArgumentError, /unknown keyword: :mixed/)
411
+ end
412
+ end
413
+
414
+ context "when mixed: true is used with map_attribute" do
415
+ it "raises an error" do
416
+ expect do
417
+ Class.new(Lutaml::Model::Serializable) do
418
+ attribute :id, :string
419
+
420
+ xml do
421
+ root "Invalid"
422
+ map_attribute :id, to: :id, mixed: true
423
+ end
424
+ end
425
+ end.to raise_error(ArgumentError, /unknown keyword: :mixed/)
426
+ end
427
+ end
428
+
429
+ context "when special char used in content with mixed true" do
430
+ let(:xml) do
431
+ <<~XML
432
+ <SpecialCharContentWithMixedTrue>
433
+ Moon&#x0026;Mars Distanced&#x00A9;its &#8212; surface covered &amp; processed
434
+ </SpecialCharContentWithMixedTrue>
435
+ XML
436
+ end
437
+
438
+ describe ".from_xml" do
439
+ let(:expected_content) { "Moon&Mars Distanced©its — surface covered & processed" }
440
+
441
+ it "deserializes special char mixed content correctly" do
442
+ parsed = MixedContentSpec::SpecialCharContentWithMixedTrue.from_xml(xml)
443
+ expect(parsed.content.strip).to eq(expected_content)
444
+ end
445
+ end
446
+
447
+ describe ".to_xml" do
448
+ let(:expected_xml) { "Moon&amp;Mars Distanced©its — surface covered &amp; processed" }
449
+
450
+ it "serializes special char mixed content correctly" do
451
+ parsed = MixedContentSpec::SpecialCharContentWithMixedTrue.from_xml(xml)
452
+ serialized = parsed.to_xml
453
+
454
+ expect(serialized).to include(expected_xml)
455
+ end
456
+ end
457
+ end
458
+
459
+ context "when special char used in content read from xml file" do
460
+ let(:fixture) { File.read(fixture_path("xml/special_char.xml")) }
461
+
462
+ describe ".from_xml" do
463
+ it "deserializes special char mixed content correctly" do
464
+ parsed = MixedContentSpec::Article.from_xml(fixture)
465
+ expect(parsed.back.ref_list.ref.element_citation.source.content).to include("R&D")
466
+ end
467
+ end
468
+
469
+ describe ".to_xml" do
470
+ it "serializes special char mixed content correctly" do
471
+ parsed = MixedContentSpec::Article.from_xml(fixture)
472
+ serialized = parsed.to_xml
473
+
474
+ expect(serialized).to include("R&amp;D")
475
+ end
476
+ end
477
+ end
478
+
479
+ context "when special char entities used with raw true" do
480
+ let(:xml) do
481
+ <<~XML
482
+ <SpecialCharContentWithRawAndMixedOption>
483
+ <special>
484
+ B <p>R&#x0026;C</p>
485
+ C <p>J&#8212;C</p>
486
+ O <p>A &amp; B </p>
487
+ F <p>Z &#x00A9;S</p>
488
+ </special>
489
+ </SpecialCharContentWithRawAndMixedOption>
490
+ XML
491
+ end
492
+
493
+ describe ".from_xml" do
494
+ 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>" }
495
+ 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>" }
496
+
497
+ it "deserializes special char mixed content correctly" do
498
+ parsed = MixedContentSpec::SpecialCharContentWithRawAndMixedOption.from_xml(xml)
499
+ expected_content = expected_nokogiri_content
500
+
501
+ if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
502
+ expected_content = expected_ox_content
503
+ parsed.special.force_encoding("UTF-8")
504
+ end
505
+
506
+ expect(parsed.special.strip).to eq(expected_content)
507
+ end
508
+ end
509
+
510
+ describe ".to_xml" do
511
+ let(:expected_nokogiri_xml) do
512
+ <<~XML
513
+ <SpecialCharContentWithRawOptionAndMixedOption><special>
514
+ B &lt;p&gt;R&amp;amp;C&lt;/p&gt;
515
+ C &lt;p&gt;J&amp;#x2014;C&lt;/p&gt;
516
+ O &lt;p&gt;A &amp;amp; B &lt;/p&gt;
517
+ F &lt;p&gt;Z &amp;#xA9;S&lt;/p&gt;
518
+ </special></SpecialCharContentWithRawOptionAndMixedOption>
519
+ XML
520
+ end
521
+
522
+ let(:expected_ox_xml) do
523
+ <<~XML
524
+ <SpecialCharContentWithRawOptionAndMixedOption>
525
+ <special> B &lt;p&gt;R&amp;amp;C&lt;/p&gt;
526
+ C &lt;p&gt;J—C&lt;/p&gt;
527
+ O &lt;p&gt;A &amp;amp; B &lt;/p&gt;
528
+ F &lt;p&gt;Z ©S&lt;/p&gt;
529
+ </special>
530
+ </SpecialCharContentWithRawOptionAndMixedOption>
531
+ XML
532
+ end
533
+
534
+ it "serializes special char mixed content correctly" do
535
+ parsed = MixedContentSpec::SpecialCharContentWithRawAndMixedOption.from_xml(xml)
536
+ serialized = parsed.to_xml
537
+
538
+ expected_output = adapter_class == Lutaml::Model::XmlAdapter::OxAdapter ? expected_ox_xml : expected_nokogiri_xml
539
+ expect(serialized).to be_equivalent_to(expected_output)
540
+ end
541
+ end
542
+ end
543
+
544
+ context "when special char used with raw true, remove & if entity not provided" do
545
+ let(:xml) do
546
+ <<~XML
547
+ <SpecialCharContentWithRawAndMixedOption>
548
+ <special>
549
+ B <p>R&C</p>
550
+ </special>
551
+ </SpecialCharContentWithRawAndMixedOption>
552
+ XML
553
+ end
554
+
555
+ describe ".from_xml" do
556
+ let(:expected_nokogiri_content) { "B <p>R</p>" }
557
+ let(:expected_ox_content) { "B <p>R&amp;C</p>" }
558
+
559
+ it "deserializes special char mixed content correctly" do
560
+ parsed = MixedContentSpec::SpecialCharContentWithRawAndMixedOption.from_xml(xml)
561
+
562
+ expected_output = adapter_class == Lutaml::Model::XmlAdapter::OxAdapter ? expected_ox_content : expected_nokogiri_content
563
+ expect(parsed.special.strip).to eq(expected_output)
564
+ end
565
+ end
566
+
567
+ describe ".to_xml" do
568
+ let(:expected_nokogiri_xml) { "B &lt;p&gt;R&lt;/p&gt;" }
569
+ let(:expected_ox_xml) { "B &lt;p&gt;R&amp;amp;C&lt;/p&gt;" }
570
+
571
+ it "serializes special char mixed content correctly" do
572
+ parsed = MixedContentSpec::SpecialCharContentWithRawAndMixedOption.from_xml(xml)
573
+ serialized = parsed.to_xml
574
+
575
+ expected_output = adapter_class == Lutaml::Model::XmlAdapter::OxAdapter ? expected_ox_xml : expected_nokogiri_xml
576
+ expect(serialized).to include(expected_output)
577
+ end
578
+ end
579
+ end
580
+
581
+ context "when special char used as full entities" do
582
+ let(:xml) do
583
+ <<~XML
584
+ <TextualSupport>
585
+ <value>&lt;computer security&gt; type of operation specified by an access right</value>
586
+ </TextualSupport>
587
+ XML
588
+ end
589
+
590
+ describe ".from_xml" do
591
+ let(:expected_content) { "<computer security> type of operation specified by an access right" }
592
+
593
+ it "deserializes special char mixed content correctly" do
594
+ parsed = MixedContentSpec::TextualSupport.from_xml(xml)
595
+
596
+ expect(parsed.value).to eq(expected_content)
597
+ end
598
+ end
599
+
600
+ describe ".to_xml" do
601
+ let(:expected_xml) { "<TextualSupport>\n <value>&lt;computer security&gt; type of operation specified by an access right</value>\n</TextualSupport>" }
602
+
603
+ it "serializes special char mixed content correctly" do
604
+ parsed = MixedContentSpec::TextualSupport.from_xml(xml)
605
+ serialized = parsed.to_xml
606
+
607
+ expect(serialized.strip).to include(expected_xml)
608
+ end
609
+ end
610
+ end
611
+ end
612
+
613
+ describe Lutaml::Model::XmlAdapter::NokogiriAdapter do
614
+ it_behaves_like "mixed content behavior", described_class
615
+ end
616
+
617
+ describe Lutaml::Model::XmlAdapter::OxAdapter do
618
+ it_behaves_like "mixed content behavior", described_class
619
+ end
620
+
621
+ # Not implemented yet
622
+ xdescribe Lutaml::Model::XmlAdapter::OgaAdapter do
623
+ it_behaves_like "mixed content behavior", described_class
624
+ end
625
+ end
@@ -0,0 +1,57 @@
1
+ require "spec_helper"
2
+
3
+ module NamespaceSpec
4
+ class NestedChild < Lutaml::Model::Serializable
5
+ attribute :name, :string
6
+
7
+ xml do
8
+ root "NestedChild"
9
+
10
+ map_element :name, to: :name
11
+ end
12
+ end
13
+
14
+ class Child < Lutaml::Model::Serializable
15
+ attribute :nested_child, NestedChild
16
+
17
+ xml do
18
+ root "NestedChild"
19
+
20
+ map_element :NestedChild, to: :nested_child
21
+ end
22
+ end
23
+
24
+ class Parent < Lutaml::Model::Serializable
25
+ attribute :child, Child
26
+
27
+ xml do
28
+ root "Parent"
29
+ namespace "https://abc.com"
30
+
31
+ map_element :Child, to: :child
32
+ end
33
+ end
34
+ end
35
+
36
+ RSpec.describe "NamespaceSpec" do
37
+ let(:parsed) { NamespaceSpec::Parent.from_xml(xml) }
38
+ let(:xml) do
39
+ <<~XML
40
+ <Parent xmlns="https://abc.com">
41
+ <Child>
42
+ <NestedChild>
43
+ <name>Rogger moore</name>
44
+ </NestedChild>
45
+ </Child>
46
+ </Parent>
47
+ XML
48
+ end
49
+
50
+ it "parses nested child using root namespace" do
51
+ expect(parsed.child.nested_child.name).to eq("Rogger moore")
52
+ end
53
+
54
+ it "round-trips xml" do
55
+ expect(parsed.to_xml).to be_equivalent_to(xml)
56
+ end
57
+ end