lutaml-model 0.8.15 → 0.8.16
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 +14 -73
- data/README.adoc +188 -25
- data/docs/_guides/xml-mapping.adoc +178 -24
- data/docs/_pages/importable_models.adoc +7 -1
- data/lib/lutaml/model/attribute.rb +4 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model.rb +2 -1
- data/lib/lutaml/turtle/transform.rb +2 -1
- data/lib/lutaml/xml/adapter/plan_based_builder.rb +3 -1
- data/lib/lutaml/xml/adapter/xml_serializer.rb +14 -4
- data/lib/lutaml/xml/adapter.rb +2 -1
- data/lib/lutaml/xml/builder/base.rb +2 -1
- data/lib/lutaml/xml/data_model.rb +19 -3
- data/lib/lutaml/xml/mapping.rb +3 -1
- data/lib/lutaml/xml/mapping_rule.rb +28 -2
- data/lib/lutaml/xml/model_transform.rb +9 -1
- data/lib/lutaml/xml/serialization/instance_methods.rb +16 -9
- data/lib/lutaml/xml/transformation/element_builder.rb +1 -3
- data/lib/lutaml/xml/transformation/rule_applier.rb +21 -0
- data/lib/lutaml/xml/transformation/rule_compiler.rb +12 -3
- data/spec/lutaml/jsonld/transform_spec.rb +3 -1
- data/spec/lutaml/model/mixed_content_spec.rb +48 -7
- data/spec/lutaml/model/raw_element_spec.rb +533 -0
- data/spec/lutaml/rdf/mapping_spec.rb +2 -1
- metadata +3 -2
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
module RawElementSpec
|
|
6
|
+
class SvgContainer < Lutaml::Model::Serializable
|
|
7
|
+
attribute :svg_data, :string
|
|
8
|
+
|
|
9
|
+
xml do
|
|
10
|
+
element "container"
|
|
11
|
+
map_element "svg", to: :svg_data, raw: :element
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class MathContainer < Lutaml::Model::Serializable
|
|
16
|
+
attribute :formula, :string
|
|
17
|
+
|
|
18
|
+
xml do
|
|
19
|
+
element "container"
|
|
20
|
+
map_element "math", to: :formula, raw: :element
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class MixedContainer < Lutaml::Model::Serializable
|
|
25
|
+
attribute :name, :string
|
|
26
|
+
attribute :embedded, :string
|
|
27
|
+
|
|
28
|
+
xml do
|
|
29
|
+
element "container"
|
|
30
|
+
map_element "name", to: :name
|
|
31
|
+
map_element "foreign", to: :embedded, raw: :element
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class NestedRawContainer < Lutaml::Model::Serializable
|
|
36
|
+
attribute :name, :string
|
|
37
|
+
attribute :embedded, :string
|
|
38
|
+
|
|
39
|
+
xml do
|
|
40
|
+
element "container"
|
|
41
|
+
map_element "name", to: :name
|
|
42
|
+
map_element "embedded", to: :embedded, raw: :element
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class MultiRawContainer < Lutaml::Model::Serializable
|
|
47
|
+
attribute :name, :string
|
|
48
|
+
attribute :svg_data, :string
|
|
49
|
+
attribute :math_data, :string
|
|
50
|
+
|
|
51
|
+
xml do
|
|
52
|
+
element "container"
|
|
53
|
+
map_element "name", to: :name
|
|
54
|
+
map_element "svg", to: :svg_data, raw: :element
|
|
55
|
+
map_element "math", to: :math_data, raw: :element
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class CollectionRawContainer < Lutaml::Model::Serializable
|
|
60
|
+
attribute :name, :string
|
|
61
|
+
attribute :fragments, :string, collection: true
|
|
62
|
+
|
|
63
|
+
xml do
|
|
64
|
+
element "container"
|
|
65
|
+
map_element "name", to: :name
|
|
66
|
+
map_element "fragment", to: :fragments, raw: :element
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class EmptyRawContainer < Lutaml::Model::Serializable
|
|
71
|
+
attribute :name, :string
|
|
72
|
+
attribute :embedded, :string
|
|
73
|
+
|
|
74
|
+
xml do
|
|
75
|
+
element "container"
|
|
76
|
+
map_element "name", to: :name
|
|
77
|
+
map_element "foreign", to: :embedded, raw: :element
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
class NormalContainer < Lutaml::Model::Serializable
|
|
82
|
+
attribute :data, :string
|
|
83
|
+
|
|
84
|
+
xml do
|
|
85
|
+
element "container"
|
|
86
|
+
map_element "data", to: :data
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
RSpec.describe "map_element raw: :element" do
|
|
91
|
+
describe "deserialization" do
|
|
92
|
+
describe "foreign namespace element capture" do
|
|
93
|
+
let(:svg_xml) do
|
|
94
|
+
<<~XML
|
|
95
|
+
<container>
|
|
96
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
|
97
|
+
<rect x="0" y="0" width="100" height="100" fill="red"/>
|
|
98
|
+
</svg>
|
|
99
|
+
</container>
|
|
100
|
+
XML
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "captures the full SVG element including its own tags" do
|
|
104
|
+
doc = SvgContainer.from_xml(svg_xml)
|
|
105
|
+
expect(doc.svg_data).to include("<svg")
|
|
106
|
+
expect(doc.svg_data).to include("</svg>")
|
|
107
|
+
expect(doc.svg_data).to include("viewBox")
|
|
108
|
+
expect(doc.svg_data).to include("<rect")
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "preserves SVG namespace attribute" do
|
|
112
|
+
doc = SvgContainer.from_xml(svg_xml)
|
|
113
|
+
expect(doc.svg_data).to include("xmlns=\"http://www.w3.org/2000/svg\"")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "preserves child elements" do
|
|
117
|
+
doc = SvgContainer.from_xml(svg_xml)
|
|
118
|
+
expect(doc.svg_data).to include("<rect")
|
|
119
|
+
expect(doc.svg_data).to include("fill=\"red\"")
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
describe "non-namespaced element capture" do
|
|
124
|
+
let(:math_xml) do
|
|
125
|
+
"<container><math><mfrac><mi>x</mi><mn>2</mn></mfrac></math></container>"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "captures the full element" do
|
|
129
|
+
doc = MathContainer.from_xml(math_xml)
|
|
130
|
+
expect(doc.formula).to include("<math>")
|
|
131
|
+
expect(doc.formula).to include("</math>")
|
|
132
|
+
expect(doc.formula).to include("<mfrac>")
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
describe "mixed mapped and raw elements" do
|
|
137
|
+
let(:mixed_xml) do
|
|
138
|
+
<<~XML
|
|
139
|
+
<container>
|
|
140
|
+
<name>test</name>
|
|
141
|
+
<foreign attr="value">
|
|
142
|
+
<child>content</child>
|
|
143
|
+
</foreign>
|
|
144
|
+
</container>
|
|
145
|
+
XML
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it "parses normal elements normally" do
|
|
149
|
+
doc = MixedContainer.from_xml(mixed_xml)
|
|
150
|
+
expect(doc.name).to eq("test")
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it "captures raw elements as full XML" do
|
|
154
|
+
doc = MixedContainer.from_xml(mixed_xml)
|
|
155
|
+
expect(doc.embedded).to include("<foreign")
|
|
156
|
+
expect(doc.embedded).to include("</foreign>")
|
|
157
|
+
expect(doc.embedded).to include("attr=\"value\"")
|
|
158
|
+
expect(doc.embedded).to include("<child>content</child>")
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
describe "nested XML in raw element" do
|
|
163
|
+
let(:nested_xml) do
|
|
164
|
+
<<~XML
|
|
165
|
+
<container>
|
|
166
|
+
<name>test</name>
|
|
167
|
+
<embedded>
|
|
168
|
+
<level1>
|
|
169
|
+
<level2 attr="deep">text</level2>
|
|
170
|
+
</level1>
|
|
171
|
+
</embedded>
|
|
172
|
+
</container>
|
|
173
|
+
XML
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it "preserves full nesting" do
|
|
177
|
+
doc = NestedRawContainer.from_xml(nested_xml)
|
|
178
|
+
expect(doc.embedded).to include("<level1>")
|
|
179
|
+
expect(doc.embedded).to include("<level2 attr=\"deep\">text</level2>")
|
|
180
|
+
expect(doc.embedded).to include("</level1>")
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
describe "multiple raw: :element mappings" do
|
|
185
|
+
let(:multi_xml) do
|
|
186
|
+
<<~XML
|
|
187
|
+
<container>
|
|
188
|
+
<name>mixed content</name>
|
|
189
|
+
<svg xmlns="http://www.w3.org/2000/svg"><circle r="5"/></svg>
|
|
190
|
+
<math><mi>π</mi></math>
|
|
191
|
+
</container>
|
|
192
|
+
XML
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it "captures each raw element independently" do
|
|
196
|
+
doc = MultiRawContainer.from_xml(multi_xml)
|
|
197
|
+
expect(doc.name).to eq("mixed content")
|
|
198
|
+
expect(doc.svg_data).to include("<svg")
|
|
199
|
+
expect(doc.svg_data).to include("<circle")
|
|
200
|
+
expect(doc.math_data).to include("<math>")
|
|
201
|
+
expect(doc.math_data).to include("<mi>")
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
describe "collection raw: :element" do
|
|
206
|
+
let(:collection_xml) do
|
|
207
|
+
<<~XML
|
|
208
|
+
<container>
|
|
209
|
+
<name>multi</name>
|
|
210
|
+
<fragment id="1">first</fragment>
|
|
211
|
+
<fragment id="2">second</fragment>
|
|
212
|
+
<fragment id="3"><nested>third</nested></fragment>
|
|
213
|
+
</container>
|
|
214
|
+
XML
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it "captures each occurrence as a collection item" do
|
|
218
|
+
doc = CollectionRawContainer.from_xml(collection_xml)
|
|
219
|
+
expect(doc.name).to eq("multi")
|
|
220
|
+
expect(doc.fragments.length).to eq(3)
|
|
221
|
+
expect(doc.fragments[0]).to include("<fragment")
|
|
222
|
+
expect(doc.fragments[0]).to include("first")
|
|
223
|
+
expect(doc.fragments[1]).to include("second")
|
|
224
|
+
expect(doc.fragments[2]).to include("<nested>third</nested>")
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
describe "missing raw element" do
|
|
229
|
+
it "returns nil when raw element is absent" do
|
|
230
|
+
doc = SvgContainer.from_xml("<container><other>text</other></container>")
|
|
231
|
+
expect(doc.svg_data).to be_nil
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
describe "empty raw element" do
|
|
236
|
+
it "captures self-closing empty element" do
|
|
237
|
+
doc = EmptyRawContainer.from_xml("<container><name>test</name><foreign/></container>")
|
|
238
|
+
expect(doc.embedded).to include("<foreign")
|
|
239
|
+
expect(doc.embedded).to include("/")
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
describe "XML special characters" do
|
|
244
|
+
it "preserves entities in raw element content" do
|
|
245
|
+
xml = "<container><foreign>a & b < c</foreign></container>"
|
|
246
|
+
doc = MixedContainer.from_xml("<container><name>test</name>#{xml.match(/<container>(.*)<\/container>/m)[1]}</container>")
|
|
247
|
+
expect(doc.embedded).to include("<foreign")
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
describe "namespace matching" do
|
|
252
|
+
it "captures element with xmlns declaration on itself" do
|
|
253
|
+
doc = SvgContainer.from_xml(
|
|
254
|
+
'<container><svg xmlns="http://www.w3.org/2000/svg"><rect/></svg></container>',
|
|
255
|
+
)
|
|
256
|
+
expect(doc.svg_data).to include("<svg")
|
|
257
|
+
expect(doc.svg_data).to include('xmlns="http://www.w3.org/2000/svg"')
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
it "captures element with no namespace" do
|
|
261
|
+
doc = SvgContainer.from_xml(
|
|
262
|
+
"<container><svg><rect/></svg></container>",
|
|
263
|
+
)
|
|
264
|
+
expect(doc.svg_data).to include("<svg")
|
|
265
|
+
expect(doc.svg_data).to include("<rect")
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
it "captures explicitly prefixed element" do
|
|
269
|
+
doc = SvgContainer.from_xml(
|
|
270
|
+
'<container xmlns:s="http://www.w3.org/2000/svg"><s:svg><s:rect/></s:svg></container>',
|
|
271
|
+
)
|
|
272
|
+
expect(doc.svg_data).to include("<s:svg")
|
|
273
|
+
expect(doc.svg_data).to include("<s:rect")
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
describe "serialization" do
|
|
279
|
+
describe "round-trip with SVG" do
|
|
280
|
+
let(:svg_xml) do
|
|
281
|
+
<<~XML
|
|
282
|
+
<container>
|
|
283
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
|
284
|
+
<rect x="0" y="0" width="100" height="100" fill="red"/>
|
|
285
|
+
</svg>
|
|
286
|
+
</container>
|
|
287
|
+
XML
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
it "preserves SVG data through parse -> serialize -> parse" do
|
|
291
|
+
doc = SvgContainer.from_xml(svg_xml)
|
|
292
|
+
output = doc.to_xml
|
|
293
|
+
doc2 = SvgContainer.from_xml(output)
|
|
294
|
+
expect(doc2.svg_data).to include("<svg")
|
|
295
|
+
expect(doc2.svg_data).to include("</svg>")
|
|
296
|
+
expect(doc2.svg_data).to include("viewBox")
|
|
297
|
+
expect(doc2.svg_data).to include("<rect")
|
|
298
|
+
expect(doc2.svg_data).to include("xmlns=\"http://www.w3.org/2000/svg\"")
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
describe "round-trip with mixed content" do
|
|
303
|
+
let(:mixed_xml) do
|
|
304
|
+
'<container><name>test</name><foreign attr="value"><child>content</child></foreign></container>'
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
it "preserves both normal and raw elements" do
|
|
308
|
+
doc = MixedContainer.from_xml(mixed_xml)
|
|
309
|
+
output = doc.to_xml
|
|
310
|
+
doc2 = MixedContainer.from_xml(output)
|
|
311
|
+
expect(doc2.name).to eq("test")
|
|
312
|
+
expect(doc2.embedded).to include("<foreign")
|
|
313
|
+
expect(doc2.embedded).to include("attr=\"value\"")
|
|
314
|
+
expect(doc2.embedded).to include("<child>content</child>")
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
describe "round-trip with nested raw XML" do
|
|
319
|
+
let(:nested_xml) do
|
|
320
|
+
'<container><name>test</name><embedded><level1><level2 attr="deep">text</level2></level1></embedded></container>'
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
it "preserves full nesting depth" do
|
|
324
|
+
doc = NestedRawContainer.from_xml(nested_xml)
|
|
325
|
+
output = doc.to_xml
|
|
326
|
+
doc2 = NestedRawContainer.from_xml(output)
|
|
327
|
+
expect(doc2.embedded).to include("<level1>")
|
|
328
|
+
expect(doc2.embedded).to include("<level2 attr=\"deep\">text</level2>")
|
|
329
|
+
expect(doc2.embedded).to include("</level1>")
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
describe "building from model instance" do
|
|
334
|
+
it "serializes a programmatically created model" do
|
|
335
|
+
model = SvgContainer.new(svg_data: '<svg xmlns="http://www.w3.org/2000/svg"><circle r="5"/></svg>')
|
|
336
|
+
xml = model.to_xml
|
|
337
|
+
expect(xml).to include("<svg")
|
|
338
|
+
expect(xml).to include("<circle")
|
|
339
|
+
expect(xml).to include("</svg>")
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
it "round-trips a programmatically created model" do
|
|
343
|
+
original_svg = '<svg xmlns="http://www.w3.org/2000/svg"><circle r="5"/></svg>'
|
|
344
|
+
model = SvgContainer.new(svg_data: original_svg)
|
|
345
|
+
xml = model.to_xml
|
|
346
|
+
doc = SvgContainer.from_xml(xml)
|
|
347
|
+
expect(doc.svg_data).to include("<svg")
|
|
348
|
+
expect(doc.svg_data).to include("<circle")
|
|
349
|
+
expect(doc.svg_data).to include("</svg>")
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
it "does not double-escape raw element content" do
|
|
353
|
+
model = SvgContainer.new(svg_data: "<svg><rect/></svg>")
|
|
354
|
+
xml = model.to_xml
|
|
355
|
+
expect(xml).not_to include("<svg")
|
|
356
|
+
expect(xml).not_to include(">")
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
describe "round-trip with collection" do
|
|
361
|
+
let(:collection_xml) do
|
|
362
|
+
<<~XML
|
|
363
|
+
<container>
|
|
364
|
+
<name>multi</name>
|
|
365
|
+
<fragment id="1">first</fragment>
|
|
366
|
+
<fragment id="2">second</fragment>
|
|
367
|
+
</container>
|
|
368
|
+
XML
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
it "preserves all collection items" do
|
|
372
|
+
doc = CollectionRawContainer.from_xml(collection_xml)
|
|
373
|
+
output = doc.to_xml
|
|
374
|
+
doc2 = CollectionRawContainer.from_xml(output)
|
|
375
|
+
expect(doc2.fragments.length).to eq(2)
|
|
376
|
+
expect(doc2.fragments[0]).to include("first")
|
|
377
|
+
expect(doc2.fragments[1]).to include("second")
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
describe "nil and empty values" do
|
|
382
|
+
it "omits raw element when value is nil" do
|
|
383
|
+
model = SvgContainer.new(svg_data: nil)
|
|
384
|
+
xml = model.to_xml
|
|
385
|
+
expect(xml).not_to include("<svg")
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
it "omits raw element when value is empty string" do
|
|
389
|
+
model = SvgContainer.new(svg_data: "")
|
|
390
|
+
xml = model.to_xml
|
|
391
|
+
expect(xml).not_to include("<svg")
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
describe "XML special characters round-trip" do
|
|
396
|
+
it "preserves angle brackets in raw element content without double-escaping" do
|
|
397
|
+
doc = MixedContainer.from_xml(
|
|
398
|
+
'<container><name>test</name><foreign><child a="1">x & y</child></foreign></container>',
|
|
399
|
+
)
|
|
400
|
+
output = doc.to_xml
|
|
401
|
+
expect(output).to include("<child")
|
|
402
|
+
expect(output).not_to include("<child")
|
|
403
|
+
doc2 = MixedContainer.from_xml(output)
|
|
404
|
+
expect(doc2.embedded).to include("<child")
|
|
405
|
+
expect(doc2.embedded).to include("x & y")
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
describe "default behavior (no raw)" do
|
|
411
|
+
it "does not capture raw XML for normal map_element" do
|
|
412
|
+
doc = NormalContainer.from_xml("<container><data>text</data></container>")
|
|
413
|
+
expect(doc.data).to eq("text")
|
|
414
|
+
expect(doc.data).not_to include("<data>")
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
it "serializes text content normally" do
|
|
418
|
+
model = NormalContainer.new(data: "hello")
|
|
419
|
+
xml = model.to_xml
|
|
420
|
+
expect(xml).to include("<data>hello</data>")
|
|
421
|
+
expect(xml).not_to include("<")
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
describe "MappingRule attributes" do
|
|
426
|
+
it "defaults raw to nil" do
|
|
427
|
+
rule = MixedContainer.mappings_for(:xml).elements.find do |r|
|
|
428
|
+
r.to == :name
|
|
429
|
+
end
|
|
430
|
+
expect(rule.raw).to be_nil
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
it "sets raw to :element when specified" do
|
|
434
|
+
rule = MixedContainer.mappings_for(:xml).elements.find do |r|
|
|
435
|
+
r.to == :embedded
|
|
436
|
+
end
|
|
437
|
+
expect(rule.raw).to eq(:element)
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
it "provides raw_element backward-compat alias" do
|
|
441
|
+
rule = MixedContainer.mappings_for(:xml).elements.find do |r|
|
|
442
|
+
r.to == :embedded
|
|
443
|
+
end
|
|
444
|
+
expect(rule.raw_element?).to be(true)
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
it "propagates raw through deep_dup" do
|
|
448
|
+
rule = MixedContainer.mappings_for(:xml).elements.find do |r|
|
|
449
|
+
r.to == :embedded
|
|
450
|
+
end
|
|
451
|
+
dup = rule.deep_dup
|
|
452
|
+
expect(dup.raw).to eq(:element)
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
describe "raw parameter validation" do
|
|
457
|
+
it "accepts :element" do
|
|
458
|
+
expect do
|
|
459
|
+
Lutaml::Xml::MappingRule.new("test", to: :x, raw: :element)
|
|
460
|
+
end.not_to raise_error
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
it "accepts :content" do
|
|
464
|
+
expect do
|
|
465
|
+
Lutaml::Xml::MappingRule.new("test", to: :x, raw: :content)
|
|
466
|
+
end.not_to raise_error
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
it "accepts nil" do
|
|
470
|
+
rule = Lutaml::Xml::MappingRule.new("test", to: :x, raw: nil)
|
|
471
|
+
expect(rule.raw).to be_nil
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
it "rejects invalid values" do
|
|
475
|
+
expect do
|
|
476
|
+
Lutaml::Xml::MappingRule.new("test", to: :x, raw: :invalid)
|
|
477
|
+
end.to raise_error(ArgumentError, /raw must be :element or :content/)
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
it "deprecates raw: true with warning" do
|
|
481
|
+
expect do
|
|
482
|
+
Lutaml::Xml::MappingRule.new("test", to: :x, raw: true)
|
|
483
|
+
end.to output(/DEPRECATED.*raw: :element/).to_stderr
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
it "treats raw: true as :element" do
|
|
487
|
+
rule = nil
|
|
488
|
+
expect do
|
|
489
|
+
rule = Lutaml::Xml::MappingRule.new("test", to: :x, raw: true)
|
|
490
|
+
end.to output(/DEPRECATED/).to_stderr
|
|
491
|
+
expect(rule.raw).to eq(:element)
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
# raw: :content mode — inner XML capture
|
|
497
|
+
module RawContentSpec
|
|
498
|
+
class StreetContainer < Lutaml::Model::Serializable
|
|
499
|
+
attribute :street, :string
|
|
500
|
+
|
|
501
|
+
xml do
|
|
502
|
+
element "address"
|
|
503
|
+
map_element "street", to: :street, raw: :content
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
RSpec.describe "map_element raw: :content" do
|
|
508
|
+
describe "deserialization" do
|
|
509
|
+
it "captures inner XML content" do
|
|
510
|
+
doc = StreetContainer.from_xml("<address><street><b>123</b> Main St</street></address>")
|
|
511
|
+
expect(doc.street).to include("<b>123</b>")
|
|
512
|
+
expect(doc.street).to include("Main St")
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
it "does not include wrapper tags" do
|
|
516
|
+
doc = StreetContainer.from_xml("<address><street><b>123</b> Main St</street></address>")
|
|
517
|
+
expect(doc.street).not_to include("<street>")
|
|
518
|
+
expect(doc.street).not_to include("</street>")
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
describe "serialization" do
|
|
523
|
+
it "round-trips inner XML content" do
|
|
524
|
+
doc = StreetContainer.from_xml("<address><street><b>123</b> Main St</street></address>")
|
|
525
|
+
output = doc.to_xml
|
|
526
|
+
doc2 = StreetContainer.from_xml(output)
|
|
527
|
+
expect(doc2.street).to include("<b>123</b>")
|
|
528
|
+
expect(doc2.street).to include("Main St")
|
|
529
|
+
end
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
end
|
|
@@ -59,7 +59,8 @@ RSpec.describe Lutaml::Rdf::Mapping do
|
|
|
59
59
|
|
|
60
60
|
it "flattens nested arrays" do
|
|
61
61
|
mapping.types(["skos:Concept", "owl:Thing"], "foaf:Person")
|
|
62
|
-
expect(mapping.rdf_type).to eq(["skos:Concept", "owl:Thing",
|
|
62
|
+
expect(mapping.rdf_type).to eq(["skos:Concept", "owl:Thing",
|
|
63
|
+
"foaf:Person"])
|
|
63
64
|
end
|
|
64
65
|
|
|
65
66
|
it "overwrites previous types on subsequent call" do
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lutaml-model
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.16
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -1628,6 +1628,7 @@ files:
|
|
|
1628
1628
|
- spec/lutaml/model/ordered_content_spec.rb
|
|
1629
1629
|
- spec/lutaml/model/polymorphic_spec.rb
|
|
1630
1630
|
- spec/lutaml/model/processing_instruction_spec.rb
|
|
1631
|
+
- spec/lutaml/model/raw_element_spec.rb
|
|
1631
1632
|
- spec/lutaml/model/register_key_value_spec.rb
|
|
1632
1633
|
- spec/lutaml/model/register_methods_spec.rb
|
|
1633
1634
|
- spec/lutaml/model/register_spec.rb
|