lutaml-model 0.5.4 → 0.6.0
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 +58 -21
- data/Gemfile +1 -0
- data/README.adoc +1112 -264
- data/lib/lutaml/model/attribute.rb +33 -10
- data/lib/lutaml/model/choice.rb +56 -0
- data/lib/lutaml/model/config.rb +1 -0
- data/lib/lutaml/model/error/choice_lower_bound_error.rb +9 -0
- data/lib/lutaml/model/error/choice_upper_bound_error.rb +9 -0
- data/lib/lutaml/model/error/import_model_with_root_error.rb +9 -0
- data/lib/lutaml/model/error/incorrect_sequence_error.rb +9 -0
- data/lib/lutaml/model/error/invalid_choice_range_error.rb +20 -0
- data/lib/lutaml/model/error/no_root_mapping_error.rb +9 -0
- data/lib/lutaml/model/error/no_root_namespace_error.rb +9 -0
- data/lib/lutaml/model/error/unknown_sequence_mapping_error.rb +9 -0
- data/lib/lutaml/model/error.rb +8 -0
- data/lib/lutaml/model/json_adapter/standard_json_adapter.rb +6 -1
- data/lib/lutaml/model/key_value_mapping.rb +3 -1
- data/lib/lutaml/model/key_value_mapping_rule.rb +4 -2
- data/lib/lutaml/model/liquefiable.rb +59 -0
- data/lib/lutaml/model/mapping_hash.rb +1 -1
- data/lib/lutaml/model/mapping_rule.rb +15 -2
- data/lib/lutaml/model/schema/xml_compiler.rb +68 -26
- data/lib/lutaml/model/schema_location.rb +7 -0
- data/lib/lutaml/model/sequence.rb +71 -0
- data/lib/lutaml/model/serialize.rb +125 -35
- data/lib/lutaml/model/type/decimal.rb +0 -4
- data/lib/lutaml/model/type/time.rb +3 -3
- data/lib/lutaml/model/utils.rb +19 -15
- data/lib/lutaml/model/validation.rb +12 -1
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/builder/oga.rb +10 -7
- data/lib/lutaml/model/xml_adapter/builder/ox.rb +20 -13
- data/lib/lutaml/model/xml_adapter/element.rb +32 -0
- data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +8 -8
- data/lib/lutaml/model/xml_adapter/oga/element.rb +14 -13
- data/lib/lutaml/model/xml_adapter/oga_adapter.rb +86 -19
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +19 -15
- data/lib/lutaml/model/xml_adapter/xml_document.rb +74 -13
- data/lib/lutaml/model/xml_adapter/xml_element.rb +57 -3
- data/lib/lutaml/model/xml_mapping.rb +49 -7
- data/lib/lutaml/model/xml_mapping_rule.rb +8 -3
- data/lib/lutaml/model.rb +1 -0
- data/lutaml-model.gemspec +5 -0
- data/spec/benchmarks/xml_parsing_benchmark_spec.rb +75 -0
- data/spec/ceramic_spec.rb +39 -0
- data/spec/fixtures/ceramic.rb +23 -0
- data/spec/fixtures/xml/address_example_260.xsd +9 -0
- data/spec/fixtures/xml/user.xsd +10 -0
- data/spec/lutaml/model/cdata_spec.rb +4 -5
- data/spec/lutaml/model/choice_spec.rb +168 -0
- data/spec/lutaml/model/collection_spec.rb +1 -1
- data/spec/lutaml/model/custom_model_spec.rb +6 -7
- data/spec/lutaml/model/custom_serialization_spec.rb +74 -2
- data/spec/lutaml/model/defaults_spec.rb +3 -1
- data/spec/lutaml/model/delegation_spec.rb +7 -5
- data/spec/lutaml/model/enum_spec.rb +35 -0
- data/spec/lutaml/model/group_spec.rb +160 -0
- data/spec/lutaml/model/inheritance_spec.rb +25 -0
- data/spec/lutaml/model/liquefiable_spec.rb +121 -0
- data/spec/lutaml/model/mixed_content_spec.rb +80 -41
- data/spec/lutaml/model/multiple_mapping_spec.rb +22 -10
- data/spec/lutaml/model/schema/xml_compiler_spec.rb +218 -25
- data/spec/lutaml/model/sequence_spec.rb +216 -0
- data/spec/lutaml/model/transformation_spec.rb +230 -0
- data/spec/lutaml/model/type_spec.rb +138 -31
- data/spec/lutaml/model/utils_spec.rb +32 -0
- data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +11 -7
- data/spec/lutaml/model/xml_mapping_rule_spec.rb +51 -0
- data/spec/lutaml/model/xml_mapping_spec.rb +143 -112
- metadata +67 -2
@@ -0,0 +1,230 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe "Value Transformations" do
|
4
|
+
module TransformationSpec
|
5
|
+
# Class with only attribute-level transformations
|
6
|
+
class AttributeTransformPerson < Lutaml::Model::Serializable
|
7
|
+
attribute :name, :string, transform: {
|
8
|
+
export: ->(value) { value.to_s.upcase },
|
9
|
+
}
|
10
|
+
attribute :email, :string, transform: {
|
11
|
+
import: ->(value) { "#{value}@example.com" },
|
12
|
+
}
|
13
|
+
attribute :tags, :string, collection: true, transform: {
|
14
|
+
export: ->(value) { value.map(&:upcase) },
|
15
|
+
import: ->(value) { value.map { |v| "#{v}-1" } },
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
# Class with only mapping-level transformations
|
20
|
+
class MappingTransformPerson < Lutaml::Model::Serializable
|
21
|
+
attribute :name, :string
|
22
|
+
attribute :email, :string
|
23
|
+
attribute :tags, :string, collection: true
|
24
|
+
|
25
|
+
json do
|
26
|
+
map "fullName", to: :name, transform: {
|
27
|
+
export: ->(value) { "Dr. #{value}" },
|
28
|
+
}
|
29
|
+
map "emailAddress", to: :email, transform: {
|
30
|
+
import: ->(value) { value.gsub("at", "@") },
|
31
|
+
}
|
32
|
+
map "labels", to: :tags, transform: {
|
33
|
+
export: ->(value) { value.join("-|-") },
|
34
|
+
import: ->(value) { value.split("|") },
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
xml do
|
39
|
+
root "person"
|
40
|
+
map_element "full-name", to: :name, transform: {
|
41
|
+
export: ->(value) { "Dr. #{value}" },
|
42
|
+
}
|
43
|
+
map_element "email-address", to: :email, transform: {
|
44
|
+
import: ->(value) { value.gsub("at", "@") },
|
45
|
+
}
|
46
|
+
map_element "labels", to: :tags, transform: {
|
47
|
+
export: ->(value) { value.join("-|-") },
|
48
|
+
import: ->(value) { value.split("|") },
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Class with both attribute and mapping transformations
|
54
|
+
class CombinedTransformPerson < Lutaml::Model::Serializable
|
55
|
+
attribute :name, :string, transform: {
|
56
|
+
export: ->(value) { value.to_s.capitalize },
|
57
|
+
import: ->(value) { value.to_s.downcase },
|
58
|
+
}
|
59
|
+
attribute :email, :string, transform: {
|
60
|
+
export: lambda(&:downcase),
|
61
|
+
import: lambda(&:downcase),
|
62
|
+
}
|
63
|
+
attribute :tags, :string, collection: true, transform: {
|
64
|
+
export: ->(value) { value.map(&:upcase) },
|
65
|
+
import: ->(value) { value.map { |v| "#{v}-1" } },
|
66
|
+
}
|
67
|
+
|
68
|
+
json do
|
69
|
+
map "fullName", to: :name, transform: {
|
70
|
+
export: ->(value) { "Prof. #{value}" },
|
71
|
+
import: ->(value) { value.gsub("Prof. ", "") },
|
72
|
+
}
|
73
|
+
map "contactEmail", to: :email, transform: {
|
74
|
+
export: ->(value) { "contact+#{value}" },
|
75
|
+
import: ->(value) { value.gsub("contact+", "") },
|
76
|
+
}
|
77
|
+
map "skills", to: :tags
|
78
|
+
end
|
79
|
+
|
80
|
+
xml do
|
81
|
+
root "person"
|
82
|
+
map_element "full-name", to: :name, transform: {
|
83
|
+
export: ->(value) { "Prof. #{value}" },
|
84
|
+
import: ->(value) { value.gsub("Prof. ", "") },
|
85
|
+
}
|
86
|
+
map_element "contact-email", to: :email, transform: {
|
87
|
+
export: ->(value) { "contact+#{value}" },
|
88
|
+
import: ->(value) { value.gsub("contact+", "") },
|
89
|
+
}
|
90
|
+
map_element "skills", to: :tags
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "Attribute-only transformations" do
|
96
|
+
let(:attribute_person) do
|
97
|
+
TransformationSpec::AttributeTransformPerson.new(
|
98
|
+
name: "john",
|
99
|
+
email: "smith",
|
100
|
+
tags: ["ruby", "rails"],
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
let(:expected_xml) do
|
105
|
+
<<~XML
|
106
|
+
<AttributeTransformPerson>
|
107
|
+
<name>JOHN</name>
|
108
|
+
<email>smith</email>
|
109
|
+
<tags>RUBY</tags>
|
110
|
+
<tags>RAILS</tags>
|
111
|
+
</AttributeTransformPerson>
|
112
|
+
XML
|
113
|
+
end
|
114
|
+
|
115
|
+
it "applies attribute transformations during serialization" do
|
116
|
+
parsed_json = JSON.parse(attribute_person.to_json)
|
117
|
+
expect(parsed_json["name"]).to eq("JOHN")
|
118
|
+
expect(parsed_json["email"]).to eq("smith")
|
119
|
+
expect(parsed_json["tags"]).to eq(["RUBY", "RAILS"])
|
120
|
+
end
|
121
|
+
|
122
|
+
it "applies attribute transformations during deserialization" do
|
123
|
+
json = {
|
124
|
+
"name" => "jane",
|
125
|
+
"email" => "doe",
|
126
|
+
"tags" => ["python", "django"],
|
127
|
+
}.to_json
|
128
|
+
|
129
|
+
parsed = TransformationSpec::AttributeTransformPerson.from_json(json)
|
130
|
+
expect(parsed.name).to eq("jane")
|
131
|
+
expect(parsed.email).to eq("doe@example.com")
|
132
|
+
expect(parsed.tags).to eq(["python-1", "django-1"])
|
133
|
+
end
|
134
|
+
|
135
|
+
it "applies attribute transformations during XML serialization" do
|
136
|
+
xml = attribute_person.to_xml
|
137
|
+
expect(xml).to be_equivalent_to(expected_xml)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "Mapping-only transformations" do
|
142
|
+
let(:mapping_person) do
|
143
|
+
TransformationSpec::MappingTransformPerson.new(
|
144
|
+
name: "alice",
|
145
|
+
email: "aliceattest.com",
|
146
|
+
tags: ["developer", "architect"],
|
147
|
+
)
|
148
|
+
end
|
149
|
+
|
150
|
+
let(:expected_xml) do
|
151
|
+
<<~XML
|
152
|
+
<person>
|
153
|
+
<full-name>Dr. alice</full-name>
|
154
|
+
<email-address>aliceattest.com</email-address>
|
155
|
+
<labels>developer-|-architect</labels>
|
156
|
+
</person>
|
157
|
+
XML
|
158
|
+
end
|
159
|
+
|
160
|
+
it "applies mapping transformations during JSON serialization" do
|
161
|
+
json = mapping_person.to_json
|
162
|
+
parsed = JSON.parse(json)
|
163
|
+
expect(parsed["fullName"]).to eq("Dr. alice")
|
164
|
+
expect(parsed["emailAddress"]).to eq("aliceattest.com")
|
165
|
+
expect(parsed["labels"]).to eq("developer-|-architect")
|
166
|
+
end
|
167
|
+
|
168
|
+
it "applies mapping transformations during JSON deserialization" do
|
169
|
+
json = {
|
170
|
+
"fullName" => "Dr. bob",
|
171
|
+
"emailAddress" => "bobattest.com",
|
172
|
+
"labels" => "senior|lead",
|
173
|
+
}.to_json
|
174
|
+
|
175
|
+
parsed = TransformationSpec::MappingTransformPerson.from_json(json)
|
176
|
+
expect(parsed.name).to eq("Dr. bob")
|
177
|
+
expect(parsed.email).to eq("bob@test.com")
|
178
|
+
expect(parsed.tags).to eq(["senior", "lead"])
|
179
|
+
end
|
180
|
+
|
181
|
+
it "applies mapping transformations during XML serialization" do
|
182
|
+
xml = mapping_person.to_xml
|
183
|
+
expect(xml).to be_equivalent_to(expected_xml)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "Combined transformations" do
|
188
|
+
let(:combined_person) do
|
189
|
+
TransformationSpec::CombinedTransformPerson.new(
|
190
|
+
name: "carol",
|
191
|
+
email: "CAROL@TEST.COM",
|
192
|
+
tags: ["manager", "agile"],
|
193
|
+
)
|
194
|
+
end
|
195
|
+
|
196
|
+
let(:expected_xml) do
|
197
|
+
<<~XML
|
198
|
+
<person>
|
199
|
+
<full-name>Prof. carol</full-name>
|
200
|
+
<contact-email>contact+CAROL@TEST.COM</contact-email>
|
201
|
+
<skills>MANAGER-1</skills>
|
202
|
+
<skills>AGILE-1</skills>
|
203
|
+
</person>
|
204
|
+
XML
|
205
|
+
end
|
206
|
+
|
207
|
+
it "applies both transformations with correct precedence in JSON" do
|
208
|
+
json = combined_person.to_json
|
209
|
+
parsed = JSON.parse(json)
|
210
|
+
|
211
|
+
expect(parsed["fullName"]).to eq("Prof. carol")
|
212
|
+
expect(parsed["contactEmail"]).to eq("contact+CAROL@TEST.COM")
|
213
|
+
end
|
214
|
+
|
215
|
+
it "handles round-trip transformations across formats" do
|
216
|
+
# JSON -> Key-Value -> JSON cycle
|
217
|
+
json = combined_person.to_json
|
218
|
+
parsed_json = TransformationSpec::CombinedTransformPerson.from_json(json)
|
219
|
+
expect(parsed_json.name).to eq("carol")
|
220
|
+
expect(parsed_json.email).to eq("CAROL@TEST.COM")
|
221
|
+
expect(parsed_json.tags).to eq(["MANAGER-1", "AGILE-1"])
|
222
|
+
end
|
223
|
+
|
224
|
+
it "applies both transformations with correct precedence in XML" do
|
225
|
+
xml = combined_person.to_xml
|
226
|
+
parsed_xml = TransformationSpec::CombinedTransformPerson.from_xml(xml)
|
227
|
+
expect(parsed_xml.to_xml).to be_equivalent_to(expected_xml)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
@@ -2,16 +2,47 @@
|
|
2
2
|
require "spec_helper"
|
3
3
|
require "bigdecimal"
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
class CustomSerializationType < Lutaml::Model::Type::Value
|
6
|
+
def self.from_xml(_xml_string)
|
7
|
+
"from_xml_overrided"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.from_json(_value)
|
11
|
+
"from_json_overrided"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.serialize(_value)
|
15
|
+
"serialize_overrided"
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_xml
|
19
|
+
"to_xml_overrided"
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_json(*_args)
|
23
|
+
"to_json_overrided"
|
9
24
|
end
|
10
25
|
end
|
11
26
|
|
12
|
-
class
|
13
|
-
|
14
|
-
|
27
|
+
class SampleModel < Lutaml::Model::Serializable
|
28
|
+
attribute :custom_type, CustomSerializationType
|
29
|
+
xml do
|
30
|
+
root "sample"
|
31
|
+
map_element "custom_type", to: :custom_type
|
32
|
+
end
|
33
|
+
json do
|
34
|
+
map_element "custom_type", to: :custom_type
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class SampleModelAttribute < Lutaml::Model::Serializable
|
39
|
+
attribute :custom_type, CustomSerializationType
|
40
|
+
xml do
|
41
|
+
root "sample"
|
42
|
+
map_attribute "custom_type", to: :custom_type
|
43
|
+
end
|
44
|
+
json do
|
45
|
+
map_element "custom_type", to: :custom_type
|
15
46
|
end
|
16
47
|
end
|
17
48
|
|
@@ -19,6 +50,17 @@ RSpec.describe Lutaml::Model::Type do
|
|
19
50
|
describe "Type System" do
|
20
51
|
describe ".register and .lookup" do
|
21
52
|
context "with valid types" do
|
53
|
+
before do
|
54
|
+
# Test class for type registration scenarios
|
55
|
+
custom_type = Class.new(Lutaml::Model::Type::Value) do
|
56
|
+
def self.cast(value)
|
57
|
+
value.to_s.upcase
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
stub_const("CustomType", custom_type)
|
62
|
+
end
|
63
|
+
|
22
64
|
it "registers and looks up a custom type" do
|
23
65
|
described_class.register(:custom, CustomType)
|
24
66
|
expect(described_class.lookup(:custom)).to eq(CustomType)
|
@@ -33,6 +75,16 @@ RSpec.describe Lutaml::Model::Type do
|
|
33
75
|
end
|
34
76
|
|
35
77
|
context "with invalid types" do
|
78
|
+
before do
|
79
|
+
invalid_type = Class.new do
|
80
|
+
def self.cast(value)
|
81
|
+
value
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
stub_const("InvalidType", invalid_type)
|
86
|
+
end
|
87
|
+
|
36
88
|
it "raises TypeError when registering non-Type::Value class" do
|
37
89
|
expect do
|
38
90
|
described_class.register(:invalid,
|
@@ -159,31 +211,35 @@ RSpec.describe Lutaml::Model::Type do
|
|
159
211
|
end
|
160
212
|
|
161
213
|
describe "Type Usage in Models" do
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
214
|
+
before do
|
215
|
+
type_test_model = Class.new(Lutaml::Model::Serializable) do
|
216
|
+
attribute :string_symbol, :string
|
217
|
+
attribute :string_class, Lutaml::Model::Type::String
|
218
|
+
attribute :integer_value, :integer
|
219
|
+
attribute :float_value, :float
|
220
|
+
attribute :date_value, :date
|
221
|
+
attribute :time_value, :time
|
222
|
+
attribute :time_without_date_value, :time_without_date
|
223
|
+
attribute :date_time_value, :date_time
|
224
|
+
attribute :boolean_value, :boolean
|
225
|
+
attribute :hash_value, :hash
|
226
|
+
|
227
|
+
xml do
|
228
|
+
root "test"
|
229
|
+
map_element "string_symbol", to: :string_symbol
|
230
|
+
map_element "string_class", to: :string_class
|
231
|
+
map_element "integer", to: :integer_value
|
232
|
+
map_element "float", to: :float_value
|
233
|
+
map_element "date", to: :date_value
|
234
|
+
map_element "time", to: :time_value
|
235
|
+
map_element "time_without_date", to: :time_without_date_value
|
236
|
+
map_element "date_time", to: :date_time_value
|
237
|
+
map_element "boolean", to: :boolean_value
|
238
|
+
map_element "hash", to: :hash_value
|
239
|
+
end
|
186
240
|
end
|
241
|
+
|
242
|
+
stub_const("TypeTestModel", type_test_model)
|
187
243
|
end
|
188
244
|
|
189
245
|
let(:test_instance) do
|
@@ -272,5 +328,56 @@ RSpec.describe Lutaml::Model::Type do
|
|
272
328
|
expect(deserialized.hash_value).to eq({ "key" => "value" })
|
273
329
|
end
|
274
330
|
end
|
331
|
+
|
332
|
+
describe "Serialization Of Custom Type" do
|
333
|
+
let(:xml) do
|
334
|
+
<<~XML
|
335
|
+
<sample>
|
336
|
+
<custom_type>test_string</custom_type>
|
337
|
+
</sample>
|
338
|
+
XML
|
339
|
+
end
|
340
|
+
|
341
|
+
let(:xml_attribute) do
|
342
|
+
<<~XML
|
343
|
+
<sample custom_type="test_string"/>
|
344
|
+
XML
|
345
|
+
end
|
346
|
+
|
347
|
+
let(:sample_instance) { SampleModel.from_xml(xml) }
|
348
|
+
let(:sample_instance_attribute) do
|
349
|
+
SampleModelAttribute.from_xml(xml_attribute)
|
350
|
+
end
|
351
|
+
|
352
|
+
it "correctly serializes to XML" do
|
353
|
+
expected_xml = <<~XML
|
354
|
+
<sample>
|
355
|
+
<custom_type>to_xml_overrided</custom_type>
|
356
|
+
</sample>
|
357
|
+
XML
|
358
|
+
expect(sample_instance.to_xml).to be_equivalent_to(expected_xml)
|
359
|
+
end
|
360
|
+
|
361
|
+
it "correctly serializes to XML attribute" do
|
362
|
+
expected_xml = <<~XML
|
363
|
+
<sample custom_type="to_xml_overrided"/>
|
364
|
+
XML
|
365
|
+
expect(sample_instance_attribute.to_xml).to be_equivalent_to(expected_xml)
|
366
|
+
end
|
367
|
+
|
368
|
+
it "correctly serializes to JSON" do
|
369
|
+
expect(sample_instance.to_json).to eq('{"custom_type":"to_json_overrided"}')
|
370
|
+
end
|
371
|
+
|
372
|
+
it "correctly deserializes from XML" do
|
373
|
+
expect(sample_instance.custom_type).to eq("from_xml_overrided")
|
374
|
+
end
|
375
|
+
|
376
|
+
it "correctly deserializes from JSON" do
|
377
|
+
json_sample_instance = SampleModel.from_json('{"custom_type":"test_string"}')
|
378
|
+
json_sample_instance.to_json
|
379
|
+
expect(json_sample_instance.custom_type).to eq("from_json_overrided")
|
380
|
+
end
|
381
|
+
end
|
275
382
|
end
|
276
383
|
end
|
@@ -54,7 +54,19 @@ RSpec.describe Lutaml::Model::Utils do
|
|
54
54
|
}
|
55
55
|
end
|
56
56
|
|
57
|
+
let(:original_array) do
|
58
|
+
[
|
59
|
+
"one", [
|
60
|
+
"one_one", [
|
61
|
+
"one_one1", "one_one2"
|
62
|
+
],
|
63
|
+
"one_two"
|
64
|
+
]
|
65
|
+
]
|
66
|
+
end
|
67
|
+
|
57
68
|
let(:duplicate_hash) { utils.deep_dup(original_hash) }
|
69
|
+
let(:duplicate_array) { utils.deep_dup(original_array) }
|
58
70
|
|
59
71
|
it "creates deep duplicate of hash" do
|
60
72
|
expect(original_hash).to eq(duplicate_hash)
|
@@ -75,5 +87,25 @@ RSpec.describe Lutaml::Model::Utils do
|
|
75
87
|
expect(original_hash[:one][:one_two]).to eq(duplicate_hash[:one][:one_two])
|
76
88
|
expect(original_hash[:one][:one_two].object_id).not_to eq(duplicate_hash[:one][:one_two].object_id)
|
77
89
|
end
|
90
|
+
|
91
|
+
it "creates deep duplicate of array" do
|
92
|
+
expect(original_array).to eq(duplicate_array)
|
93
|
+
expect(original_array.object_id).not_to eq(duplicate_array.object_id)
|
94
|
+
|
95
|
+
expect(original_array[0]).to eq(duplicate_array[0])
|
96
|
+
expect(original_array[0].object_id).not_to eq(duplicate_array[0].object_id)
|
97
|
+
|
98
|
+
expect(original_array[1][0]).to eq(duplicate_array[1][0])
|
99
|
+
expect(original_array[1][0].object_id).not_to eq(duplicate_array[1][0].object_id)
|
100
|
+
|
101
|
+
expect(original_array[1][1][0]).to eq(duplicate_array[1][1][0])
|
102
|
+
expect(original_array[1][1][0].object_id).not_to eq(duplicate_array[1][1][0].object_id)
|
103
|
+
|
104
|
+
expect(original_array[1][1][1]).to eq(duplicate_array[1][1][1])
|
105
|
+
expect(original_array[1][1][1].object_id).not_to eq(duplicate_array[1][1][1].object_id)
|
106
|
+
|
107
|
+
expect(original_array[1][2]).to eq(duplicate_array[1][2])
|
108
|
+
expect(original_array[1][2].object_id).not_to eq(duplicate_array[1][2].object_id)
|
109
|
+
end
|
78
110
|
end
|
79
111
|
end
|
@@ -14,7 +14,7 @@ RSpec.describe Lutaml::Model::XmlAdapter::OgaAdapter do
|
|
14
14
|
let(:document) { described_class.parse(xml_string) }
|
15
15
|
|
16
16
|
context "parsing XML with namespaces" do
|
17
|
-
let(:child) { document.root.children
|
17
|
+
let(:child) { document.root.children.first }
|
18
18
|
|
19
19
|
it "parses the root element with default namespace" do
|
20
20
|
expect(document.root.name).to eq("root")
|
@@ -39,18 +39,22 @@ RSpec.describe Lutaml::Model::XmlAdapter::OgaAdapter do
|
|
39
39
|
|
40
40
|
context "generating XML with namespaces" do
|
41
41
|
it "generates XML with namespaces correctly" do
|
42
|
-
xml_output = document.to_xml
|
43
|
-
parsed_output = Oga.
|
42
|
+
xml_output = document.root.to_xml
|
43
|
+
parsed_output = Moxml::Adapter::Oga.parse(xml_output)
|
44
44
|
|
45
45
|
root = parsed_output.children.first
|
46
46
|
expect(root.name).to eq("root")
|
47
47
|
expect(root.namespace.uri).to eq("http://example.com/default")
|
48
48
|
|
49
|
-
child = root.children
|
50
|
-
expect(child
|
49
|
+
child = root.children.first
|
50
|
+
expect(described_class.prefixed_name_of(child)).to eq("prefix:child")
|
51
51
|
expect(child.namespace.uri).to eq("http://example.com/prefixed")
|
52
|
-
|
53
|
-
expect(
|
52
|
+
unprefixed_attr = child.attributes.find { |attr| attr.name == "attr" }
|
53
|
+
expect(unprefixed_attr.value).to eq("value")
|
54
|
+
prefixed_attr = child.attributes.find do |attr|
|
55
|
+
described_class.prefixed_name_of(attr) == "prefix:attr"
|
56
|
+
end
|
57
|
+
expect(prefixed_attr.value).to eq("prefixed_value")
|
54
58
|
end
|
55
59
|
end
|
56
60
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "lutaml/model/xml_adapter/ox_adapter"
|
4
|
+
require "lutaml/model/xml_adapter/oga_adapter"
|
5
|
+
|
6
|
+
require_relative "../../../lib/lutaml/model/xml_mapping_rule"
|
7
|
+
|
8
|
+
def content_to_xml(model, parent, doc)
|
9
|
+
content = model.all_content.sub(/^<div>/, "").sub(/<\/div>$/, "")
|
10
|
+
doc.add_xml_fragment(parent, content)
|
11
|
+
end
|
12
|
+
|
13
|
+
def content_from_xml(model, value)
|
14
|
+
model.all_content = "<div>#{value}</div>"
|
15
|
+
end
|
16
|
+
|
17
|
+
RSpec.describe Lutaml::Model::XmlMappingRule do
|
18
|
+
context "with Xml Mapping Rule" do
|
19
|
+
let(:orig_mapping_rule) do
|
20
|
+
described_class.new(
|
21
|
+
"name",
|
22
|
+
to: :name,
|
23
|
+
render_nil: true,
|
24
|
+
render_default: true,
|
25
|
+
with: { to: :content_to_xml, from: :content_from_xml },
|
26
|
+
delegate: true,
|
27
|
+
namespace: "http://child-namespace",
|
28
|
+
prefix: "cn",
|
29
|
+
mixed_content: true,
|
30
|
+
cdata: true,
|
31
|
+
namespace_set: true,
|
32
|
+
prefix_set: true,
|
33
|
+
attribute: true,
|
34
|
+
default_namespace: "http://parent-namespace",
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
let(:dup_mapping_rule) do
|
39
|
+
orig_mapping_rule.deep_dup
|
40
|
+
end
|
41
|
+
|
42
|
+
it "duplicates all instance variables" do
|
43
|
+
orig_mapping_rule.instance_variables.each do |variable|
|
44
|
+
orig_var = orig_mapping_rule.instance_variable_get(variable)
|
45
|
+
dup_var = dup_mapping_rule.instance_variable_get(variable)
|
46
|
+
|
47
|
+
expect(orig_var).to eq(dup_var)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|