lutaml-model 0.3.23 → 0.3.25

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