lutaml-model 0.8.15 → 0.8.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +0 -3
  3. data/.rubocop_todo.yml +49 -52
  4. data/README.adoc +188 -25
  5. data/docs/_guides/xml/namespace-semantics.adoc +2 -0
  6. data/docs/_guides/xml-mapping.adoc +178 -24
  7. data/docs/_guides/xml-namespace-qualification.adoc +142 -0
  8. data/docs/_pages/importable_models.adoc +7 -1
  9. data/docs/_tutorials/xml-element-attribute-namespace-guide.adoc +2 -0
  10. data/docs/_tutorials/xml-schema-primer-style-guide.adoc +2 -0
  11. data/docs/namespace-management.adoc +2 -0
  12. data/docs/xml-schema-qualification.md +6 -0
  13. data/lib/lutaml/jsonld.rb +1 -4
  14. data/lib/lutaml/model/attribute.rb +4 -0
  15. data/lib/lutaml/model/choice.rb +34 -0
  16. data/lib/lutaml/model/compiled_rule.rb +7 -0
  17. data/lib/lutaml/model/version.rb +1 -1
  18. data/lib/lutaml/model.rb +3 -1
  19. data/lib/lutaml/{jsonld → rdf}/context.rb +1 -1
  20. data/lib/lutaml/{jsonld/transform.rb → rdf/linked_data_transform.rb} +33 -35
  21. data/lib/lutaml/{jsonld → rdf}/term_definition.rb +1 -1
  22. data/lib/lutaml/rdf.rb +3 -0
  23. data/lib/lutaml/turtle/transform.rb +2 -1
  24. data/lib/lutaml/xml/adapter/plan_based_builder.rb +3 -1
  25. data/lib/lutaml/xml/adapter/xml_serializer.rb +14 -4
  26. data/lib/lutaml/xml/adapter.rb +2 -1
  27. data/lib/lutaml/xml/adapter_element.rb +2 -2
  28. data/lib/lutaml/xml/builder/base.rb +2 -1
  29. data/lib/lutaml/xml/data_model.rb +19 -3
  30. data/lib/lutaml/xml/mapping.rb +3 -1
  31. data/lib/lutaml/xml/mapping_rule.rb +28 -2
  32. data/lib/lutaml/xml/model_transform.rb +9 -1
  33. data/lib/lutaml/xml/serialization/instance_methods.rb +16 -9
  34. data/lib/lutaml/xml/transformation/element_builder.rb +39 -26
  35. data/lib/lutaml/xml/transformation/rule_applier.rb +21 -0
  36. data/lib/lutaml/xml/transformation/rule_compiler.rb +12 -3
  37. data/lib/lutaml/yamlld/adapter.rb +25 -0
  38. data/lib/lutaml/yamlld.rb +25 -0
  39. data/spec/lutaml/integration/multi_format_spec.rb +23 -0
  40. data/spec/lutaml/model/attribute_spec.rb +8 -1
  41. data/spec/lutaml/model/choice_restrict_spec.rb +225 -0
  42. data/spec/lutaml/model/custom_model_spec.rb +4 -4
  43. data/spec/lutaml/model/mixed_content_spec.rb +50 -9
  44. data/spec/lutaml/model/multiple_mapping_spec.rb +4 -4
  45. data/spec/lutaml/model/ordered_content_spec.rb +3 -3
  46. data/spec/lutaml/model/raw_element_spec.rb +533 -0
  47. data/spec/lutaml/model/uninitialized_class_spec.rb +1 -1
  48. data/spec/lutaml/model/xsd_form_default_patterns_spec.rb +2 -2
  49. data/spec/lutaml/model/xsd_patterns_spec.rb +4 -4
  50. data/spec/lutaml/{jsonld → rdf}/context_spec.rb +2 -2
  51. data/spec/lutaml/{jsonld/transform_spec.rb → rdf/linked_data_transform_spec.rb} +18 -2
  52. data/spec/lutaml/rdf/mapping_spec.rb +2 -1
  53. data/spec/lutaml/{jsonld → rdf}/term_definition_spec.rb +2 -2
  54. data/spec/lutaml/turtle/transform_spec.rb +2 -2
  55. data/spec/lutaml/xml/enhanced_mapping_spec.rb +230 -1
  56. data/spec/lutaml/xml/mapping_spec.rb +4 -4
  57. data/spec/lutaml/xml/namespace_placement_spec.rb +11 -8
  58. data/spec/lutaml/xml/namespace_three_phase_spec.rb +1 -1
  59. data/spec/lutaml/xml/prefix_control_spec.rb +4 -4
  60. data/spec/lutaml/xml/serializable_namespace_spec.rb +6 -6
  61. data/spec/lutaml/xml/type_namespace_examples_spec.rb +1 -1
  62. data/spec/lutaml/xml/type_namespace_integration_spec.rb +3 -3
  63. data/spec/lutaml/yamlld/adapter_spec.rb +56 -0
  64. data/spec/lutaml/yamlld/registration_spec.rb +33 -0
  65. metadata +14 -8
@@ -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>&#x3C0;</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 &amp; b &lt; 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("&lt;svg")
356
+ expect(xml).not_to include("&gt;")
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 &amp; y</child></foreign></container>',
399
+ )
400
+ output = doc.to_xml
401
+ expect(output).to include("<child")
402
+ expect(output).not_to include("&lt;child")
403
+ doc2 = MixedContainer.from_xml(output)
404
+ expect(doc2.embedded).to include("<child")
405
+ expect(doc2.embedded).to include("x &amp; 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("&lt;")
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
@@ -23,7 +23,7 @@ RSpec.describe Lutaml::Model::UninitializedClass do
23
23
 
24
24
  describe "#match?" do
25
25
  it "returns false for any argument" do
26
- expect(uninitialized).not_to match /pattern/
26
+ expect(uninitialized).not_to include("pattern")
27
27
  expect(uninitialized).not_to match "string"
28
28
  expect(uninitialized).not_to match nil
29
29
  end
@@ -85,7 +85,7 @@ RSpec.describe "XSD Form Default Patterns" do
85
85
  xml = instance.to_xml
86
86
 
87
87
  # The element should be unqualified (no prefix) in instance
88
- expect(xml).to match(%r{<premium})
88
+ expect(xml).to include("<premium")
89
89
  expect(xml).to include("<elementFormUnqualified")
90
90
  end
91
91
  end
@@ -281,7 +281,7 @@ RSpec.describe "XSD Form Default Patterns" do
281
281
  # and the namespace is already declared on an ancestor
282
282
  # The child element should be in the blank namespace without xmlns="" declaration
283
283
  # because the parent uses prefix format (xmlns:ex) or the namespace is already declared.
284
- expect(result).not_to match(%r{xmlns=""})
284
+ expect(result).not_to include('xmlns=""')
285
285
  end
286
286
 
287
287
  it "serializes root element with namespace declaration" do
@@ -43,7 +43,7 @@ RSpec.describe "XSD Three Pattern Architecture" do
43
43
 
44
44
  expect(xsd).to include('<element name="item">')
45
45
  expect(xsd).to include("<complexType>")
46
- expect(xsd).not_to match(/<complexType name=/)
46
+ expect(xsd).not_to include("<complexType name=")
47
47
  expect(xsd).to include('<attribute name="id" type="xs:string"/>')
48
48
  end
49
49
  end
@@ -68,7 +68,7 @@ RSpec.describe "XSD Three Pattern Architecture" do
68
68
  expect(xsd).to include('<element name="name" type="xs:string"/>')
69
69
  expect(xsd).to include('<element name="value" type="xs:integer"/>')
70
70
  # Should NOT generate standalone element declaration
71
- expect(xsd).not_to match(/<element name="product"/)
71
+ expect(xsd).not_to include('<element name="product"')
72
72
  end
73
73
 
74
74
  it "type-only model can be referenced by other elements" do
@@ -233,7 +233,7 @@ RSpec.describe "XSD Three Pattern Architecture" do
233
233
 
234
234
  xsd = Lutaml::Model::Schema.to_xsd(klass)
235
235
  expect(xsd).to include('<element name="simple">')
236
- expect(xsd).not_to match(/<element name="simple" type=/)
236
+ expect(xsd).not_to include('<element name="simple" type=')
237
237
  end
238
238
 
239
239
  it "Pattern 2: type_name only -> named reusable" do
@@ -252,7 +252,7 @@ RSpec.describe "XSD Three Pattern Architecture" do
252
252
  xsd = Lutaml::Model::Schema.to_xsd(klass)
253
253
  expect(xsd).to include('<complexType name="SimpleType">')
254
254
  # Should not have standalone element declaration (only child elements in sequence are OK)
255
- expect(xsd).not_to match(/<element name="SimpleType"/)
255
+ expect(xsd).not_to include('<element name="SimpleType"')
256
256
  end
257
257
 
258
258
  it "Pattern 3: both -> element with named type" do
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "spec_helper"
4
- require "lutaml/jsonld"
4
+ require "lutaml/rdf"
5
5
 
6
- RSpec.describe Lutaml::JsonLd::Context do
6
+ RSpec.describe Lutaml::Rdf::Context do
7
7
  subject(:ctx) { described_class.new }
8
8
 
9
9
  describe "empty context" do
@@ -2,8 +2,9 @@
2
2
 
3
3
  require "spec_helper"
4
4
  require "lutaml/jsonld"
5
+ require "lutaml/yamlld"
5
6
 
6
- RSpec.describe Lutaml::JsonLd::Transform do
7
+ RSpec.describe Lutaml::Rdf::LinkedDataTransform do
7
8
  before do
8
9
  stub_const("TestSkosNs", Class.new(Lutaml::Rdf::Namespace) do
9
10
  uri "http://www.w3.org/2004/02/skos/core#"
@@ -138,6 +139,19 @@ RSpec.describe Lutaml::JsonLd::Transform do
138
139
  expect(parsed["@context"]["skos"]).to eq("http://www.w3.org/2004/02/skos/core#")
139
140
  expect(parsed["@context"]["ex"]).to eq("http://example.org/")
140
141
  end
142
+
143
+ it "preserves data through model → YAML-LD → model" do
144
+ yaml = instance.to_yamlld
145
+ restored = JsonLdTestModel.from_yamlld(yaml)
146
+ expect(restored.name).to eq("test")
147
+ expect(restored.description).to eq("A test concept")
148
+ end
149
+
150
+ it "produces the same data model from YAML-LD as JSON-LD" do
151
+ from_yamlld = YAML.safe_load(instance.to_yamlld)
152
+ from_jsonld = JSON.parse(instance.to_jsonld)
153
+ expect(from_yamlld).to eq(from_jsonld)
154
+ end
141
155
  end
142
156
 
143
157
  describe "nil attribute values" do
@@ -492,7 +506,9 @@ RSpec.describe Lutaml::JsonLd::Transform do
492
506
  ],
493
507
  )
494
508
  parsed = JSON.parse(parent.to_jsonld)
495
- parent_resource = parsed["@graph"].find { |r| r["@type"] == "skos:Collection" }
509
+ parent_resource = parsed["@graph"].find do |r|
510
+ r["@type"] == "skos:Collection"
511
+ end
496
512
  expect(parent_resource["member"]).to eq([
497
513
  { "@id" => "http://example.org/item/a" },
498
514
  { "@id" => "http://example.org/item/b" },
@@ -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", "foaf:Person"])
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
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "spec_helper"
4
- require "lutaml/jsonld"
4
+ require "lutaml/rdf"
5
5
 
6
- RSpec.describe Lutaml::JsonLd::TermDefinition do
6
+ RSpec.describe Lutaml::Rdf::TermDefinition do
7
7
  it "simple term maps to name => id" do
8
8
  td = described_class.new(name: "name", id: "http://example.org/name")
9
9
  expect(td.to_context_hash).to eq("name" => "http://example.org/name")