lutaml-model 0.3.23 → 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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +35 -16
- data/README.adoc +274 -28
- data/lib/lutaml/model/attribute.rb +18 -8
- data/lib/lutaml/model/error/type_error.rb +9 -0
- data/lib/lutaml/model/error/unknown_type_error.rb +9 -0
- data/lib/lutaml/model/error/validation_error.rb +0 -1
- data/lib/lutaml/model/error.rb +2 -0
- data/lib/lutaml/model/serialize.rb +7 -2
- data/lib/lutaml/model/type/boolean.rb +38 -0
- data/lib/lutaml/model/type/date.rb +35 -0
- data/lib/lutaml/model/type/date_time.rb +32 -4
- data/lib/lutaml/model/type/decimal.rb +42 -0
- data/lib/lutaml/model/type/float.rb +37 -0
- data/lib/lutaml/model/type/hash.rb +62 -0
- data/lib/lutaml/model/type/integer.rb +41 -0
- data/lib/lutaml/model/type/string.rb +49 -0
- data/lib/lutaml/model/type/time.rb +49 -0
- data/lib/lutaml/model/type/time_without_date.rb +37 -5
- data/lib/lutaml/model/type/value.rb +52 -0
- data/lib/lutaml/model/type.rb +50 -114
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +5 -2
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +2 -1
- data/lib/lutaml/model/xml_adapter/xml_document.rb +0 -2
- data/lutaml-model.gemspec +1 -1
- data/spec/address_spec.rb +170 -0
- data/spec/fixtures/address.rb +33 -0
- data/spec/fixtures/person.rb +73 -0
- data/spec/fixtures/sample_model.rb +40 -0
- data/spec/fixtures/vase.rb +38 -0
- data/spec/fixtures/xml/special_char.xml +13 -0
- data/spec/lutaml/model/attribute_spec.rb +112 -0
- data/spec/lutaml/model/collection_spec.rb +299 -0
- data/spec/lutaml/model/comparable_model_spec.rb +106 -0
- data/spec/lutaml/model/custom_model_spec.rb +410 -0
- data/spec/lutaml/model/custom_serialization_spec.rb +170 -0
- data/spec/lutaml/model/defaults_spec.rb +221 -0
- data/spec/lutaml/model/delegation_spec.rb +340 -0
- data/spec/lutaml/model/inheritance_spec.rb +92 -0
- data/spec/lutaml/model/json_adapter_spec.rb +37 -0
- data/spec/lutaml/model/key_value_mapping_spec.rb +86 -0
- data/spec/lutaml/model/map_content_spec.rb +118 -0
- data/spec/lutaml/model/mixed_content_spec.rb +625 -0
- data/spec/lutaml/model/namespace_spec.rb +57 -0
- data/spec/lutaml/model/ordered_content_spec.rb +83 -0
- data/spec/lutaml/model/render_nil_spec.rb +138 -0
- data/spec/lutaml/model/schema/json_schema_spec.rb +79 -0
- data/spec/lutaml/model/schema/relaxng_schema_spec.rb +60 -0
- data/spec/lutaml/model/schema/xsd_schema_spec.rb +55 -0
- data/spec/lutaml/model/schema/yaml_schema_spec.rb +47 -0
- data/spec/lutaml/model/serializable_spec.rb +297 -0
- data/spec/lutaml/model/serializable_validation_spec.rb +85 -0
- data/spec/lutaml/model/simple_model_spec.rb +314 -0
- data/spec/lutaml/model/toml_adapter_spec.rb +39 -0
- data/spec/lutaml/model/type/boolean_spec.rb +54 -0
- data/spec/lutaml/model/type/date_spec.rb +118 -0
- data/spec/lutaml/model/type/date_time_spec.rb +127 -0
- data/spec/lutaml/model/type/decimal_spec.rb +125 -0
- data/spec/lutaml/model/type/float_spec.rb +191 -0
- data/spec/lutaml/model/type/hash_spec.rb +63 -0
- data/spec/lutaml/model/type/integer_spec.rb +145 -0
- data/spec/lutaml/model/type/string_spec.rb +150 -0
- data/spec/lutaml/model/type/time_spec.rb +142 -0
- data/spec/lutaml/model/type/time_without_date_spec.rb +125 -0
- data/spec/lutaml/model/type_spec.rb +276 -0
- data/spec/lutaml/model/utils_spec.rb +79 -0
- data/spec/lutaml/model/validation_spec.rb +83 -0
- data/spec/lutaml/model/with_child_mapping_spec.rb +174 -0
- data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +56 -0
- data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +56 -0
- data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +61 -0
- data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +251 -0
- data/spec/lutaml/model/xml_adapter_spec.rb +178 -0
- data/spec/lutaml/model/xml_mapping_spec.rb +863 -0
- data/spec/lutaml/model/yaml_adapter_spec.rb +30 -0
- data/spec/lutaml/model_spec.rb +1 -0
- data/spec/person_spec.rb +161 -0
- data/spec/spec_helper.rb +33 -0
- 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<sub>2</sub>text<sup>1</sup>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 <b>web developer</b> with <strong>years</strong> of
|
792
|
+
<i>experience</i> 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&C</p>
|
844
|
+
C <p>J—C</p>
|
845
|
+
O <p>A & B </p>
|
846
|
+
F <p>Z © </p>
|
847
|
+
</SpecialCharContentWithMapAll>
|
848
|
+
XML
|
849
|
+
|
850
|
+
expected_xml = <<~XML.strip
|
851
|
+
<SpecialCharContentWithMapAll>
|
852
|
+
B <p>R&amp;C</p>
|
853
|
+
C <p>J&#x2014;C</p>
|
854
|
+
O <p>A &amp; B </p>
|
855
|
+
F <p>Z &#xA9; </p>
|
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
|