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,863 @@
1
+ require "spec_helper"
2
+ require_relative "../../../lib/lutaml/model/xml_mapping"
3
+ require_relative "../../../lib/lutaml/model/xml_mapping_rule"
4
+
5
+ # Define a sample class for testing map_content
6
+ class Italic < Lutaml::Model::Serializable
7
+ attribute :text, Lutaml::Model::Type::String, collection: true
8
+
9
+ xml do
10
+ root "i"
11
+ map_content to: :text
12
+ end
13
+ end
14
+
15
+ # Define a sample class for testing p tag
16
+ class Paragraph < Lutaml::Model::Serializable
17
+ attribute :text, Lutaml::Model::Type::String
18
+ attribute :paragraph, Paragraph
19
+
20
+ xml do
21
+ root "p"
22
+
23
+ map_content to: :text
24
+ map_element "p", to: :paragraph
25
+ end
26
+ end
27
+
28
+ module XmlMapping
29
+ class ChildNamespaceNil < Lutaml::Model::Serializable
30
+ attribute :element_default_namespace, :string
31
+ attribute :element_nil_namespace, :string
32
+ attribute :element_new_namespace, :string
33
+
34
+ xml do
35
+ root "ChildNamespaceNil"
36
+ namespace "http://www.omg.org/spec/XMI/20131001", "xmi"
37
+
38
+ # this will inherit the namespace from the parent i.e <xmi:ElementDefaultNamespace>
39
+ map_element "ElementDefaultNamespace", to: :element_default_namespace
40
+
41
+ # this will have nil namesapce applied i.e <ElementNilNamespace>
42
+ map_element "ElementNilNamespace", to: :element_nil_namespace,
43
+ prefix: nil,
44
+ namespace: nil
45
+
46
+ # this will have new namespace i.e <new:ElementNewNamespace>
47
+ map_element "ElementNewNamespace", to: :element_new_namespace,
48
+ prefix: "new",
49
+ namespace: "http://www.omg.org/spec/XMI/20161001"
50
+ end
51
+ end
52
+
53
+ class Address < Lutaml::Model::Serializable
54
+ attribute :street, ::Lutaml::Model::Type::String, raw: true
55
+ attribute :city, :string, raw: true
56
+ attribute :address, Address
57
+
58
+ xml do
59
+ root "address"
60
+
61
+ map_element "street", to: :street
62
+ map_element "city", to: :city
63
+ end
64
+ end
65
+
66
+ class Person < Lutaml::Model::Serializable
67
+ attribute :name, Lutaml::Model::Type::String
68
+ attribute :address, XmlMapping::Address
69
+ end
70
+
71
+ class Mfenced < Lutaml::Model::Serializable
72
+ attribute :open, :string
73
+
74
+ xml do
75
+ root "mfenced"
76
+ map_attribute "open", to: :open
77
+ end
78
+ end
79
+
80
+ class MmlMath < Lutaml::Model::Serializable
81
+ attribute :mfenced, Mfenced
82
+
83
+ xml do
84
+ root "math"
85
+ namespace "http://www.w3.org/1998/Math/MathML"
86
+ map_element :mfenced, to: :mfenced
87
+ end
88
+ end
89
+
90
+ class AttributeNamespace < Lutaml::Model::Serializable
91
+ attribute :alpha, :string
92
+ attribute :beta, :string
93
+
94
+ xml do
95
+ root "example"
96
+ namespace "http://www.check.com", "ns1"
97
+
98
+ map_attribute "alpha", to: :alpha,
99
+ namespace: "http://www.example.com",
100
+ prefix: "ex1"
101
+
102
+ map_attribute "beta", to: :beta
103
+ end
104
+ end
105
+
106
+ class SameNameDifferentNamespace < Lutaml::Model::Serializable
107
+ attribute :gml_application_schema, :string
108
+ attribute :citygml_application_schema, :string
109
+ attribute :application_schema, :string
110
+ attribute :app, :string
111
+
112
+ xml do
113
+ root "SameElementName"
114
+ namespace "http://www.omg.org/spec/XMI/20131001", nil
115
+
116
+ map_element "ApplicationSchema", to: :gml_application_schema,
117
+ namespace: "http://www.sparxsystems.com/profiles/GML/1.0",
118
+ prefix: "GML"
119
+
120
+ map_element "ApplicationSchema", to: :citygml_application_schema,
121
+ namespace: "http://www.sparxsystems.com/profiles/CityGML/1.0",
122
+ prefix: "CityGML"
123
+
124
+ map_element "ApplicationSchema", to: :application_schema
125
+
126
+ map_attribute "App", to: :app
127
+ end
128
+ end
129
+
130
+ class SchemaLocationOrdered < Lutaml::Model::Serializable
131
+ attribute :content, :string
132
+ attribute :second, SchemaLocationOrdered
133
+
134
+ xml do
135
+ root "schemaLocationOrdered", mixed: true
136
+
137
+ map_content to: :content
138
+ map_element "schemaLocationOrdered", to: :second
139
+ end
140
+ end
141
+
142
+ class ToBeDuplicated < Lutaml::Model::Serializable
143
+ attribute :content, :string
144
+ attribute :element, :string
145
+ attribute :attribute, :string
146
+
147
+ xml do
148
+ root "ToBeDuplicated"
149
+ namespace "https://testing-duplicate", "td"
150
+
151
+ map_content to: :content
152
+ map_attribute "attribute", to: :attribute
153
+ map_element "element", to: :element,
154
+ namespace: "https://test-element",
155
+ prefix: "te"
156
+ end
157
+ end
158
+
159
+ class SpecialCharContentWithMapAll < Lutaml::Model::Serializable
160
+ attribute :all_content, :string
161
+
162
+ xml do
163
+ root "SpecialCharContentWithMapAll"
164
+
165
+ map_all to: :all_content
166
+ end
167
+ end
168
+
169
+ class WithMapAll < Lutaml::Model::Serializable
170
+ attribute :all_content, :string
171
+
172
+ xml do
173
+ root "WithMapAll"
174
+
175
+ map_all to: :all_content
176
+ end
177
+ end
178
+
179
+ class WithoutMapAll < Lutaml::Model::Serializable
180
+ attribute :content, :string
181
+
182
+ xml do
183
+ root "WithoutMapAll"
184
+
185
+ map_content to: :content
186
+ end
187
+ end
188
+
189
+ class WithNestedMapAll < Lutaml::Model::Serializable
190
+ attribute :age, :integer
191
+ attribute :name, :string
192
+ attribute :description, WithMapAll
193
+
194
+ xml do
195
+ root "WithNestedMapAll"
196
+
197
+ map_attribute :age, to: :age
198
+ map_element :name, to: :name
199
+ map_element :description, to: :description
200
+ end
201
+ end
202
+
203
+ class WithChildExplicitNamespace < Lutaml::Model::Serializable
204
+ attribute :with_default_namespace, :string
205
+ attribute :with_namespace, :string
206
+ attribute :without_namespace, :string
207
+
208
+ xml do
209
+ root "WithChildExplicitNamespaceNil"
210
+ namespace "http://parent-namespace", "pn"
211
+
212
+ map_element "DefaultNamespace", to: :with_default_namespace
213
+
214
+ map_element "WithNamespace", to: :with_namespace,
215
+ namespace: "http://child-namespace",
216
+ prefix: "cn"
217
+
218
+ map_element "WithoutNamespace", to: :without_namespace,
219
+ namespace: nil,
220
+ prefix: nil
221
+ end
222
+ end
223
+ end
224
+
225
+ RSpec.describe Lutaml::Model::XmlMapping do
226
+ let(:mapping) { described_class.new }
227
+
228
+ context "attribute namespace" do
229
+ input_xml = '<ns1:example ex1:alpha="hello" beta="bye" xmlns:ns1="http://www.check.com" xmlns:ex1="http://www.example.com"></ns1:example>'
230
+
231
+ it "checks the attribute with and without namespace" do
232
+ parsed = XmlMapping::AttributeNamespace.from_xml(input_xml)
233
+ expect(parsed.alpha).to eq("hello")
234
+ expect(parsed.beta).to eq("bye")
235
+ expect(parsed.to_xml).to be_equivalent_to(input_xml)
236
+ end
237
+ end
238
+
239
+ context "explicit namespace" do
240
+ mml = '<math xmlns="http://www.w3.org/1998/Math/MathML"><mfenced open="("></mfenced></math>'
241
+
242
+ it "nil namespace" do
243
+ parsed = XmlMapping::MmlMath.from_xml(mml)
244
+ expect(parsed.to_xml).to be_equivalent_to(mml)
245
+ end
246
+ end
247
+
248
+ context "with same name elements" do
249
+ let(:input_xml) do
250
+ <<~XML
251
+ <SameElementName App="hello" xmlns="http://www.omg.org/spec/XMI/20131001" xmlns:GML="http://www.sparxsystems.com/profiles/GML/1.0" xmlns:CityGML="http://www.sparxsystems.com/profiles/CityGML/1.0">
252
+ <GML:ApplicationSchema>GML App</GML:ApplicationSchema>
253
+ <CityGML:ApplicationSchema>CityGML App</CityGML:ApplicationSchema>
254
+ <ApplicationSchema>App</ApplicationSchema>
255
+ </SameElementName>
256
+ XML
257
+ end
258
+
259
+ it "parses XML and serializes elements with the same name" do
260
+ parsed = XmlMapping::SameNameDifferentNamespace.from_xml(input_xml)
261
+
262
+ expect(parsed.citygml_application_schema).to eq("CityGML App")
263
+ expect(parsed.gml_application_schema).to eq("GML App")
264
+ expect(parsed.application_schema).to eq("App")
265
+ expect(parsed.app).to eq("hello")
266
+ expect(parsed.element_order).to eq(["text", "ApplicationSchema", "text",
267
+ "ApplicationSchema", "text", "ApplicationSchema", "text"])
268
+ expect(XmlMapping::SameNameDifferentNamespace.from_xml(input_xml).to_xml).to be_equivalent_to(input_xml)
269
+ end
270
+ end
271
+
272
+ context "with elements have different prefixed namespaces" do
273
+ before do
274
+ mapping.root("XMI")
275
+ mapping.namespace("http://www.omg.org/spec/XMI/20131001")
276
+ mapping.map_element(
277
+ "ApplicationSchema",
278
+ to: :gml_application_schema,
279
+ namespace: "http://www.sparxsystems.com/profiles/GML/1.0",
280
+ prefix: "GML",
281
+ )
282
+ mapping.map_element(
283
+ "ApplicationSchema",
284
+ to: :citygml_application_schema,
285
+ namespace: "http://www.sparxsystems.com/profiles/CityGML/1.0",
286
+ prefix: "CityGML",
287
+ )
288
+ mapping.map_element(
289
+ "ApplicationSchema",
290
+ to: :citygml_application_schema,
291
+ namespace: "http://www.sparxsystems.com/profiles/CGML/1.0",
292
+ prefix: "CGML",
293
+ )
294
+ end
295
+
296
+ it "maps elements correctly" do
297
+ expect(mapping.elements[0].namespace).to eq("http://www.sparxsystems.com/profiles/GML/1.0")
298
+ expect(mapping.elements[1].namespace).to eq("http://www.sparxsystems.com/profiles/CityGML/1.0")
299
+ expect(mapping.elements[2].namespace).to eq("http://www.sparxsystems.com/profiles/CGML/1.0")
300
+ expect(mapping.elements.size).to eq(3)
301
+ end
302
+ end
303
+
304
+ context "with child having explicit namespaces" do
305
+ let(:xml) do
306
+ <<~XML.strip
307
+ <pn:WithChildExplicitNamespaceNil xmlns:pn="http://parent-namespace" xmlns:cn="http://child-namespace">
308
+ <pn:DefaultNamespace>default namespace text</pn:DefaultNamespace>
309
+ <cn:WithNamespace>explicit namespace text</cn:WithNamespace>
310
+ <WithoutNamespace>without namespace text</WithoutNamespace>
311
+ </pn:WithChildExplicitNamespaceNil>
312
+ XML
313
+ end
314
+
315
+ let(:parsed) do
316
+ XmlMapping::WithChildExplicitNamespace.from_xml(xml)
317
+ end
318
+
319
+ it "reads element with default namespace" do
320
+ expect(parsed.with_default_namespace).to eq("default namespace text")
321
+ end
322
+
323
+ it "reads element with explicit namespace" do
324
+ expect(parsed.with_namespace).to eq("explicit namespace text")
325
+ end
326
+
327
+ it "reads element without namespace" do
328
+ expect(parsed.without_namespace).to eq("without namespace text")
329
+ end
330
+
331
+ it "round-trips xml with child explicit namespace" do
332
+ expect(parsed.to_xml).to be_equivalent_to(xml)
333
+ end
334
+ end
335
+
336
+ context "with default namespace" do
337
+ before do
338
+ mapping.root("ceramic")
339
+ mapping.namespace("https://example.com/ceramic/1.2")
340
+ mapping.map_element("type", to: :type)
341
+ mapping.map_element("color", to: :color, delegate: :glaze)
342
+ mapping.map_element("finish", to: :finish, delegate: :glaze)
343
+ end
344
+
345
+ it "sets the default namespace for the root element" do
346
+ expect(mapping.namespace_uri).to eq("https://example.com/ceramic/1.2")
347
+ expect(mapping.namespace_prefix).to be_nil
348
+ end
349
+
350
+ it "maps elements correctly" do
351
+ expect(mapping.elements.size).to eq(3)
352
+ expect(mapping.elements[0].name).to eq("type")
353
+ expect(mapping.elements[1].delegate).to eq(:glaze)
354
+ end
355
+ end
356
+
357
+ context "with prefixed namespace" do
358
+ before do
359
+ mapping.root("ceramic")
360
+ mapping.namespace("https://example.com/ceramic/1.2", "cera")
361
+ mapping.map_element("type", to: :type)
362
+ mapping.map_element("color", to: :color, delegate: :glaze)
363
+ mapping.map_element("finish", to: :finish, delegate: :glaze)
364
+ end
365
+
366
+ it "sets the namespace with prefix for the root element" do
367
+ expect(mapping.namespace_uri).to eq("https://example.com/ceramic/1.2")
368
+ expect(mapping.namespace_prefix).to eq("cera")
369
+ end
370
+
371
+ it "maps elements correctly" do
372
+ expect(mapping.elements.size).to eq(3)
373
+ expect(mapping.elements[0].name).to eq("type")
374
+ expect(mapping.elements[1].delegate).to eq(:glaze)
375
+ end
376
+ end
377
+
378
+ context "with element-level namespace" do
379
+ before do
380
+ mapping.root("ceramic")
381
+ mapping.map_element(
382
+ "type",
383
+ to: :type,
384
+ namespace: "https://example.com/ceramic/1.2",
385
+ prefix: "cera",
386
+ )
387
+ mapping.map_element("color", to: :color, delegate: :glaze)
388
+ mapping.map_element("finish", to: :finish, delegate: :glaze)
389
+ end
390
+
391
+ it "sets the namespace for individual elements" do
392
+ expect(mapping.elements.size).to eq(3)
393
+ expect(mapping.elements[0].namespace).to eq("https://example.com/ceramic/1.2")
394
+ expect(mapping.elements[0].prefix).to eq("cera")
395
+ expect(mapping.elements[1].delegate).to eq(:glaze)
396
+ end
397
+ end
398
+
399
+ context "with attribute-level namespace" do
400
+ before do
401
+ mapping.root("ceramic")
402
+ mapping.map_attribute(
403
+ "date",
404
+ to: :date,
405
+ namespace: "https://example.com/ceramic/1.2",
406
+ prefix: "cera",
407
+ )
408
+ mapping.map_element("type", to: :type)
409
+ mapping.map_element("color", to: :color, delegate: :glaze)
410
+ mapping.map_element("finish", to: :finish, delegate: :glaze)
411
+ end
412
+
413
+ it "sets the namespace for individual attributes" do
414
+ expect(mapping.attributes.size).to eq(1)
415
+ expect(mapping.attributes[0].namespace).to eq("https://example.com/ceramic/1.2")
416
+ expect(mapping.attributes[0].prefix).to eq("cera")
417
+ end
418
+ end
419
+
420
+ context "with nil element-level namespace" do
421
+ let(:expected_xml) do
422
+ <<~XML
423
+ <xmi:ChildNamespaceNil xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:new="http://www.omg.org/spec/XMI/20161001">
424
+ <xmi:ElementDefaultNamespace>Default namespace</xmi:ElementDefaultNamespace>
425
+ <ElementNilNamespace>No namespace</ElementNilNamespace>
426
+ <new:ElementNewNamespace>New namespace</new:ElementNewNamespace>
427
+ </xmi:ChildNamespaceNil>
428
+ XML
429
+ end
430
+
431
+ let(:model) do
432
+ XmlMapping::ChildNamespaceNil.new(
433
+ {
434
+ element_default_namespace: "Default namespace",
435
+ element_nil_namespace: "No namespace",
436
+ element_new_namespace: "New namespace",
437
+ },
438
+ )
439
+ end
440
+
441
+ it "expect to apply correct namespaces" do
442
+ expect(model.to_xml).to be_equivalent_to(expected_xml)
443
+ end
444
+ end
445
+
446
+ context "with schemaLocation" do
447
+ context "when mixed: false" do
448
+ let(:xml) do
449
+ <<~XML
450
+ <p xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
451
+ xsi:schemaLocation="http://www.opengis.net/gml/3.2 http://schemas.opengis.net/gml/3.2.1/gml.xsd">
452
+ <p xmlns:xsi="http://another-instance"
453
+ xsi:schemaLocation="http://www.opengis.net/gml/3.7">
454
+ Some text inside paragraph
455
+ </p>
456
+ </p>
457
+ XML
458
+ end
459
+
460
+ it "contain schemaLocation attributes" do
461
+ expect(Paragraph.from_xml(xml).to_xml).to be_equivalent_to(xml)
462
+ end
463
+ end
464
+
465
+ context "when mixed: true" do
466
+ let(:xml) do
467
+ <<~XML
468
+ <schemaLocationOrdered xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
469
+ xsi:schemaLocation="http://www.opengis.net/gml/3.2 http://schemas.opengis.net/gml/3.2.1/gml.xsd">
470
+ <schemaLocationOrdered xmlns:xsi="http://another-instance"
471
+ xsi:schemaLocation="http://www.opengis.net/gml/3.7">
472
+ Some text inside paragraph
473
+ </schemaLocationOrdered>
474
+ </schemaLocationOrdered>
475
+ XML
476
+ end
477
+
478
+ it "contain schemaLocation attributes" do
479
+ expect(XmlMapping::SchemaLocationOrdered.from_xml(xml).to_xml).to be_equivalent_to(xml)
480
+ end
481
+ end
482
+ end
483
+
484
+ context "with multiple schemaLocations" do
485
+ let(:xml) do
486
+ <<~XML
487
+ <p xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
488
+ xsi:schemaLocation="http://www.opengis.net/gml/3.2 http://schemas.opengis.net/gml/3.2.1/gml.xsd http://www.w3.org/1999/xlink http://www.w3.org/1999/xlink.xsd">
489
+ <p xmlns:xsi="http://another-instance"
490
+ xsi:schemaLocation="http://www.opengis.net/gml/3.7 http://schemas.opengis.net/gml/3.7.1/gml.xsd http://www.isotc211.org/2005/gmd http://schemas.opengis.net/iso/19139/20070417/gmd/gmd.xsd">
491
+ Some text inside paragraph
492
+ </p>
493
+ </p>
494
+ XML
495
+ end
496
+
497
+ it "parses and serializes multiple schemaLocation attributes" do
498
+ parsed = Paragraph.from_xml(xml)
499
+ expect(parsed.schema_location.size).to eq(2)
500
+ expect(parsed.schema_location[0].namespace).to eq("http://www.opengis.net/gml/3.2")
501
+ expect(parsed.schema_location[0].location).to eq("http://schemas.opengis.net/gml/3.2.1/gml.xsd")
502
+ expect(parsed.schema_location[1].namespace).to eq("http://www.w3.org/1999/xlink")
503
+ expect(parsed.schema_location[1].location).to eq("http://www.w3.org/1999/xlink.xsd")
504
+
505
+ serialized = parsed.to_xml
506
+ expect(serialized).to be_equivalent_to(xml)
507
+ expect(serialized).to include('xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"')
508
+ expect(serialized).to include('xsi:schemaLocation="http://www.opengis.net/gml/3.2 http://schemas.opengis.net/gml/3.2.1/gml.xsd http://www.w3.org/1999/xlink http://www.w3.org/1999/xlink.xsd"')
509
+ end
510
+
511
+ it "handles nested elements with different schemaLocations" do
512
+ parsed = Paragraph.from_xml(xml)
513
+ nested_p = parsed.paragraph
514
+
515
+ expect(nested_p).to be_a(Paragraph)
516
+ expect(nested_p.schema_location.size).to eq(2)
517
+ expect(nested_p.schema_location[0].namespace).to eq("http://www.opengis.net/gml/3.7")
518
+ expect(nested_p.schema_location[0].location).to eq("http://schemas.opengis.net/gml/3.7.1/gml.xsd")
519
+ expect(nested_p.schema_location[1].namespace).to eq("http://www.isotc211.org/2005/gmd")
520
+ expect(nested_p.schema_location[1].location).to eq("http://schemas.opengis.net/iso/19139/20070417/gmd/gmd.xsd")
521
+
522
+ serialized = parsed.to_xml
523
+ expect(serialized).to include('xmlns:xsi="http://another-instance"')
524
+ expect(serialized).to include('xsi:schemaLocation="http://www.opengis.net/gml/3.7 http://schemas.opengis.net/gml/3.7.1/gml.xsd http://www.isotc211.org/2005/gmd http://schemas.opengis.net/iso/19139/20070417/gmd/gmd.xsd"')
525
+ end
526
+
527
+ it "creates XML with multiple schemaLocations" do
528
+ paragraph = Paragraph.new(
529
+ schema_location: Lutaml::Model::SchemaLocation.new(
530
+ schema_location: {
531
+ "http://www.opengis.net/gml/3.2" => "http://schemas.opengis.net/gml/3.2.1/gml.xsd",
532
+ "http://www.w3.org/1999/xlink" => "http://www.w3.org/1999/xlink.xsd",
533
+ },
534
+ prefix: "xsi",
535
+ ),
536
+ paragraph: Paragraph.new(
537
+ schema_location: Lutaml::Model::SchemaLocation.new(
538
+ schema_location: {
539
+ "http://www.opengis.net/gml/3.7" => "http://schemas.opengis.net/gml/3.7.1/gml.xsd",
540
+ "http://www.isotc211.org/2005/gmd" => "http://schemas.opengis.net/iso/19139/20070417/gmd/gmd.xsd",
541
+ },
542
+ prefix: "xsi",
543
+ namespace: "http://another-instance",
544
+ ),
545
+ text: ["Some text inside paragraph"],
546
+ ),
547
+ )
548
+
549
+ serialized = paragraph.to_xml
550
+ expect(serialized).to be_equivalent_to(xml)
551
+ end
552
+ end
553
+
554
+ context "with raw mapping" do
555
+ let(:input_xml) do
556
+ <<~XML
557
+ <person>
558
+ <name>John Doe</name>
559
+ <address>
560
+ <street>
561
+ <a>N</a>
562
+ <p>adf</p>
563
+ </street>
564
+ <city><a>M</a></city>
565
+ </address>
566
+ </person>
567
+ XML
568
+ end
569
+
570
+ let(:expected_street) do
571
+ if Lutaml::Model::Config.xml_adapter == Lutaml::Model::XmlAdapter::OxAdapter
572
+ "<a>N</a>\n<p>adf</p>\n"
573
+ else
574
+ "\n <a>N</a>\n <p>adf</p>\n "
575
+ end
576
+ end
577
+
578
+ let(:model) { XmlMapping::Person.from_xml(input_xml) }
579
+
580
+ it "expect to contain raw xml" do
581
+ expect(model.address.street).to eq(expected_street)
582
+ expect(model.address.city.strip).to eq("<a>M</a>")
583
+ end
584
+ end
585
+
586
+ context "with content mapping" do
587
+ let(:xml_data) { "<i>my text <b>bold</b> is in italics</i>" }
588
+ let(:italic) { Italic.from_xml(xml_data) }
589
+
590
+ it "parses the textual content of an XML element" do
591
+ expect(italic.text).to eq(["my text ", " is in italics"])
592
+ end
593
+ end
594
+
595
+ context "with p object" do
596
+ describe "convert from xml containing p tag" do
597
+ let(:xml_data) { "<p>my text for paragraph</p>" }
598
+ let(:paragraph) { Paragraph.from_xml(xml_data) }
599
+
600
+ it "parses the textual content of an XML element" do
601
+ expect(paragraph.text).to eq("my text for paragraph")
602
+ end
603
+ end
604
+
605
+ describe "generate xml with p tag" do
606
+ let(:paragraph) { Paragraph.new(text: "my text for paragraph") }
607
+ let(:expected_xml) { "<p>my text for paragraph</p>" }
608
+
609
+ it "converts to xml correctly" do
610
+ expect(paragraph.to_xml).to eq(expected_xml)
611
+ end
612
+ end
613
+ end
614
+
615
+ describe "#deep_dup" do
616
+ let(:orig_mappings) do
617
+ XmlMapping::ToBeDuplicated.mappings_for(:xml)
618
+ end
619
+
620
+ let(:dup_mappings) do
621
+ orig_mappings.deep_dup
622
+ end
623
+
624
+ it "duplicates root_element" do
625
+ orig_root = orig_mappings.root_element
626
+ dup_root = dup_mappings.root_element
627
+
628
+ expect(orig_root).to eq(dup_root)
629
+ expect(orig_root.object_id).not_to eq(dup_root.object_id)
630
+ end
631
+
632
+ it "duplicates namespace_uri" do
633
+ orig_namespace_uri = orig_mappings.namespace_uri
634
+ dup_namespace_uri = dup_mappings.namespace_uri
635
+
636
+ expect(orig_namespace_uri).to eq(dup_namespace_uri)
637
+ expect(orig_namespace_uri.object_id).not_to eq(dup_namespace_uri.object_id)
638
+ end
639
+
640
+ it "duplicates namespace_prefix" do
641
+ orig_namespace_prefix = orig_mappings.namespace_prefix
642
+ dup_namespace_prefix = dup_mappings.namespace_prefix
643
+
644
+ expect(orig_namespace_prefix).to eq(dup_namespace_prefix)
645
+ expect(orig_namespace_prefix.object_id).not_to eq(dup_namespace_prefix.object_id)
646
+ end
647
+
648
+ context "when duplicating mapping" do
649
+ let(:orig_mapping) { orig_mappings.mappings[0] }
650
+ let(:dup_mapping) { dup_mappings.mappings[0] }
651
+
652
+ it "duplicates custom_methods" do
653
+ orig_custom_methods = orig_mapping.custom_methods
654
+ dup_custom_methods = dup_mapping.custom_methods
655
+
656
+ expect(orig_custom_methods).to eq(dup_custom_methods)
657
+ expect(orig_custom_methods.object_id).not_to eq(dup_custom_methods.object_id)
658
+ end
659
+
660
+ it "duplicates default_namespace" do
661
+ orig_default_namespace = orig_mapping.default_namespace
662
+ dup_default_namespace = dup_mapping.default_namespace
663
+
664
+ expect(orig_default_namespace).to eq(dup_default_namespace)
665
+ expect(orig_default_namespace.object_id).not_to eq(dup_default_namespace.object_id)
666
+ end
667
+
668
+ it "duplicates delegate" do
669
+ # `delegate` is symbol which are constant so object_id will be same
670
+ expect(orig_mapping.delegate).to eq(dup_mapping.delegate)
671
+ end
672
+
673
+ it "duplicates mixed_content" do
674
+ # boolean value is constant so object_id will be same
675
+ expect(orig_mapping.mixed_content).to eq(dup_mapping.mixed_content)
676
+ end
677
+
678
+ it "duplicates name" do
679
+ orig_name = orig_mapping.name
680
+ dup_name = dup_mapping.name
681
+
682
+ expect(orig_name).to eq(dup_name)
683
+ expect(orig_name.object_id).not_to eq(dup_name.object_id)
684
+ end
685
+
686
+ it "duplicates namespace" do
687
+ orig_namespace = orig_mapping.namespace
688
+ dup_namespace = dup_mapping.namespace
689
+
690
+ expect(orig_namespace).to eq(dup_namespace)
691
+ expect(orig_namespace.object_id).not_to eq(dup_namespace.object_id)
692
+ end
693
+
694
+ it "duplicates namespace_set" do
695
+ # boolean value is constant so object_id will be same
696
+ expect(orig_mapping.namespace_set?).to eq(dup_mapping.namespace_set?)
697
+ end
698
+
699
+ it "duplicates prefix" do
700
+ orig_prefix = orig_mapping.prefix
701
+ dup_prefix = dup_mapping.prefix
702
+
703
+ expect(orig_prefix).to eq(dup_prefix)
704
+ expect(orig_prefix.object_id).not_to eq(dup_prefix.object_id)
705
+ end
706
+
707
+ it "duplicates prefix_set" do
708
+ # boolean value is constant so object_id will be same
709
+ expect(orig_mapping.prefix_set?).to eq(dup_mapping.prefix_set?)
710
+ end
711
+
712
+ it "duplicates render_nil" do
713
+ # boolean value is constant so object_id will be same
714
+ expect(orig_mapping.render_nil?).to eq(dup_mapping.render_nil?)
715
+ end
716
+
717
+ it "duplicates to" do
718
+ # `to` is symbol which are constant so object_id will be same
719
+ expect(orig_mapping.to).to eq(dup_mapping.to)
720
+ end
721
+ end
722
+ end
723
+
724
+ describe "#map_all" do
725
+ context "when map_all is defined before any other mapping" do
726
+ let(:error_message) { "no other mappings are allowed with map_all" }
727
+
728
+ it "raise error when for map_element with map_all" do
729
+ expect do
730
+ XmlMapping::WithMapAll.xml do
731
+ map_element "ele", to: :ele
732
+ end
733
+ end.to raise_error(StandardError, error_message)
734
+ end
735
+
736
+ it "raise error when for map_attribute with map_all" do
737
+ expect do
738
+ XmlMapping::WithMapAll.xml do
739
+ map_attribute "attr", to: :attr
740
+ end
741
+ end.to raise_error(StandardError, error_message)
742
+ end
743
+
744
+ it "raise error when for map_content with map_all" do
745
+ expect do
746
+ XmlMapping::WithMapAll.xml do
747
+ map_content to: :text
748
+ end
749
+ end.to raise_error(StandardError, error_message)
750
+ end
751
+ end
752
+
753
+ context "when map_all is defined after other mappings" do
754
+ let(:error_message) { "map_all is not allowed with other mappings" }
755
+
756
+ it "raise error when for map_element with map_all" do
757
+ expect do
758
+ XmlMapping::WithoutMapAll.xml do
759
+ map_all to: :all_content
760
+ end
761
+ end.to raise_error(StandardError, error_message)
762
+ end
763
+ end
764
+
765
+ it "maps all the content including tags" do
766
+ inner_xml = "Str<sub>2</sub>text<sup>1</sup>123"
767
+ xml = "<WithMapAll>#{inner_xml}</WithMapAll>"
768
+
769
+ parsed = XmlMapping::WithMapAll.from_xml(xml)
770
+
771
+ expect(parsed.all_content).to eq(inner_xml)
772
+ end
773
+
774
+ it "round-trips xml" do
775
+ xml = "<WithMapAll>Str<sub>2</sub>text<sup>1</sup>123</WithMapAll>"
776
+ expected_xml = "<WithMapAll>Str&lt;sub&gt;2&lt;/sub&gt;text&lt;sup&gt;1&lt;/sup&gt;123</WithMapAll>"
777
+
778
+ expect(XmlMapping::WithMapAll.from_xml(xml).to_xml).to eq(expected_xml)
779
+ end
780
+
781
+ context "when nested content has map_all" do
782
+ let(:description) do
783
+ <<~DESCRIPTION
784
+ I'm a <b>web developer</b> with <strong>years</strong> of
785
+ <i>experience</i> in many programing languages.
786
+ DESCRIPTION
787
+ end
788
+
789
+ let(:expected_description) do
790
+ <<~DESCRIPTION
791
+ I'm a &lt;b&gt;web developer&lt;/b&gt; with &lt;strong&gt;years&lt;/strong&gt; of
792
+ &lt;i&gt;experience&lt;/i&gt; in many programing languages.
793
+ DESCRIPTION
794
+ end
795
+
796
+ let(:xml) do
797
+ <<~XML
798
+ <WithNestedMapAll age="23">
799
+ <name>John Doe</name>
800
+ <description>
801
+ #{description}
802
+ </description>
803
+ </WithNestedMapAll>
804
+ XML
805
+ end
806
+
807
+ let(:expected_xml) do
808
+ <<~XML
809
+ <WithNestedMapAll age="23">
810
+ <name>John Doe</name>
811
+ <description>
812
+ #{expected_description}
813
+ </description>
814
+ </WithNestedMapAll>
815
+ XML
816
+ end
817
+
818
+ let(:parsed) do
819
+ XmlMapping::WithNestedMapAll.from_xml(xml)
820
+ end
821
+
822
+ it "maps description correctly" do
823
+ expect(parsed.description.all_content.strip).to eq(description.strip)
824
+ end
825
+
826
+ it "maps name correctly" do
827
+ expect(parsed.name).to eq("John Doe")
828
+ end
829
+
830
+ it "maps age correctly" do
831
+ expect(parsed.age).to eq(23)
832
+ end
833
+
834
+ it "round-trips xml" do
835
+ expect(parsed.to_xml).to be_equivalent_to(expected_xml)
836
+ end
837
+ end
838
+
839
+ context "when special char used in content with map all" do
840
+ it "round-trips xml" do
841
+ xml = <<~XML
842
+ <SpecialCharContentWithMapAll>
843
+ B <p>R&#x0026;C</p>
844
+ C <p>J&#8212;C</p>
845
+ O <p>A &amp; B </p>
846
+ F <p>Z &#x00A9; </p>
847
+ </SpecialCharContentWithMapAll>
848
+ XML
849
+
850
+ expected_xml = <<~XML.strip
851
+ <SpecialCharContentWithMapAll>
852
+ B &lt;p&gt;R&amp;amp;C&lt;/p&gt;
853
+ C &lt;p&gt;J&amp;#x2014;C&lt;/p&gt;
854
+ O &lt;p&gt;A &amp;amp; B &lt;/p&gt;
855
+ F &lt;p&gt;Z &amp;#xA9; &lt;/p&gt;
856
+ </SpecialCharContentWithMapAll>
857
+ XML
858
+
859
+ expect(XmlMapping::SpecialCharContentWithMapAll.from_xml(xml).to_xml).to eq(expected_xml)
860
+ end
861
+ end
862
+ end
863
+ end