lutaml-model 0.8.3 → 0.8.5

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 (132) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-tests.yml +3 -1
  3. data/.rubocop.yml +18 -0
  4. data/.rubocop_todo.yml +16 -22
  5. data/Gemfile +2 -0
  6. data/README.adoc +327 -3
  7. data/docs/_guides/document-validation.adoc +303 -0
  8. data/docs/_guides/index.adoc +19 -0
  9. data/docs/_guides/jsonld-serialization.adoc +217 -0
  10. data/docs/_guides/rdf-serialization.adoc +344 -0
  11. data/docs/_guides/turtle-serialization.adoc +224 -0
  12. data/docs/_guides/xml-mapping.adoc +9 -1
  13. data/docs/_guides/xml_mappings/07_best_practices.adoc +36 -0
  14. data/docs/_guides/xml_mappings/08_troubleshooting.adoc +89 -0
  15. data/docs/_pages/serialization_adapters.adoc +31 -0
  16. data/docs/_references/index.adoc +1 -0
  17. data/docs/_references/rdf-namespaces.adoc +243 -0
  18. data/docs/_tutorials/lutaml-xml-architecture.adoc +6 -1
  19. data/docs/index.adoc +3 -2
  20. data/lib/lutaml/jsonld/adapter.rb +23 -0
  21. data/lib/lutaml/jsonld/context.rb +69 -0
  22. data/lib/lutaml/jsonld/term_definition.rb +39 -0
  23. data/lib/lutaml/jsonld/transform.rb +174 -0
  24. data/lib/lutaml/jsonld.rb +23 -0
  25. data/lib/lutaml/model/attribute.rb +19 -1
  26. data/lib/lutaml/model/error/liquid_drop_already_registered_error.rb +11 -0
  27. data/lib/lutaml/model/error/ordered_content_mapping_error.rb +17 -0
  28. data/lib/lutaml/model/format_registry.rb +10 -1
  29. data/lib/lutaml/model/global_context.rb +1 -0
  30. data/lib/lutaml/model/liquefiable.rb +12 -15
  31. data/lib/lutaml/model/mapping/mapping_rule.rb +10 -2
  32. data/lib/lutaml/model/mapping_hash.rb +1 -1
  33. data/lib/lutaml/model/serialize/format_conversion.rb +17 -1
  34. data/lib/lutaml/model/services/transformer.rb +67 -32
  35. data/lib/lutaml/model/transform.rb +41 -4
  36. data/lib/lutaml/model/uninitialized_class.rb +11 -5
  37. data/lib/lutaml/model/validation/concerns/has_issues.rb +27 -0
  38. data/lib/lutaml/model/validation/context.rb +36 -0
  39. data/lib/lutaml/model/validation/issue.rb +62 -0
  40. data/lib/lutaml/model/validation/layer_result.rb +34 -0
  41. data/lib/lutaml/model/validation/profile.rb +66 -0
  42. data/lib/lutaml/model/validation/registry.rb +60 -0
  43. data/lib/lutaml/model/validation/remediation.rb +33 -0
  44. data/lib/lutaml/model/validation/remediation_result.rb +20 -0
  45. data/lib/lutaml/model/validation/report.rb +39 -0
  46. data/lib/lutaml/model/validation/rule.rb +59 -0
  47. data/lib/lutaml/model/validation.rb +2 -1
  48. data/lib/lutaml/model/validation_framework.rb +77 -0
  49. data/lib/lutaml/model/version.rb +1 -1
  50. data/lib/lutaml/model.rb +10 -0
  51. data/lib/lutaml/rdf/error.rb +7 -0
  52. data/lib/lutaml/rdf/iri.rb +44 -0
  53. data/lib/lutaml/rdf/language_tagged.rb +11 -0
  54. data/lib/lutaml/rdf/literal.rb +62 -0
  55. data/lib/lutaml/rdf/mapping.rb +71 -0
  56. data/lib/lutaml/rdf/mapping_rule.rb +35 -0
  57. data/lib/lutaml/rdf/member_rule.rb +13 -0
  58. data/lib/lutaml/rdf/namespace.rb +58 -0
  59. data/lib/lutaml/rdf/namespace_set.rb +69 -0
  60. data/lib/lutaml/rdf/namespaces/dcterms_namespace.rb +12 -0
  61. data/lib/lutaml/rdf/namespaces/owl_namespace.rb +12 -0
  62. data/lib/lutaml/rdf/namespaces/rdf_namespace.rb +14 -0
  63. data/lib/lutaml/rdf/namespaces/rdfs_namespace.rb +12 -0
  64. data/lib/lutaml/rdf/namespaces/skos_namespace.rb +12 -0
  65. data/lib/lutaml/rdf/namespaces/xsd_namespace.rb +12 -0
  66. data/lib/lutaml/rdf/namespaces.rb +14 -0
  67. data/lib/lutaml/rdf/transform.rb +36 -0
  68. data/lib/lutaml/rdf.rb +19 -0
  69. data/lib/lutaml/turtle/adapter.rb +35 -0
  70. data/lib/lutaml/turtle/mapping.rb +7 -0
  71. data/lib/lutaml/turtle/transform.rb +158 -0
  72. data/lib/lutaml/turtle.rb +22 -0
  73. data/lib/lutaml/xml/adapter/nokogiri_adapter.rb +9 -2
  74. data/lib/lutaml/xml/adapter/oga_adapter.rb +11 -3
  75. data/lib/lutaml/xml/adapter/ox_adapter.rb +5 -2
  76. data/lib/lutaml/xml/adapter/rexml_adapter.rb +10 -3
  77. data/lib/lutaml/xml/adapter_element.rb +26 -2
  78. data/lib/lutaml/xml/data_model.rb +14 -0
  79. data/lib/lutaml/xml/document.rb +3 -0
  80. data/lib/lutaml/xml/element.rb +8 -2
  81. data/lib/lutaml/xml/mapping.rb +9 -0
  82. data/lib/lutaml/xml/model_transform.rb +42 -0
  83. data/lib/lutaml/xml/schema/xsd/base.rb +4 -1
  84. data/lib/lutaml/xml/serialization/instance_methods.rb +3 -1
  85. data/lib/lutaml/xml/transformation/ordered_applier.rb +46 -2
  86. data/lib/lutaml/xml/transformation.rb +40 -1
  87. data/lib/lutaml/xml/xml_element.rb +8 -7
  88. data/lutaml-model.gemspec +1 -1
  89. data/spec/lutaml/integration/edge_cases_spec.rb +109 -0
  90. data/spec/lutaml/integration/multi_format_spec.rb +106 -0
  91. data/spec/lutaml/integration/round_trip_spec.rb +170 -0
  92. data/spec/lutaml/jsonld/adapter_spec.rb +46 -0
  93. data/spec/lutaml/jsonld/context_spec.rb +114 -0
  94. data/spec/lutaml/jsonld/term_definition_spec.rb +55 -0
  95. data/spec/lutaml/jsonld/transform_spec.rb +211 -0
  96. data/spec/lutaml/model/attribute_default_cache_spec.rb +58 -0
  97. data/spec/lutaml/model/liquefiable_spec.rb +22 -6
  98. data/spec/lutaml/model/liquid_compatibility_spec.rb +442 -0
  99. data/spec/lutaml/model/ordered_content_spec.rb +5 -5
  100. data/spec/lutaml/model/services/transformer_spec.rb +43 -0
  101. data/spec/lutaml/model/transform_cache_spec.rb +62 -0
  102. data/spec/lutaml/model/transform_dynamic_attributes_spec.rb +41 -0
  103. data/spec/lutaml/model/uninitialized_class_deep_dup_spec.rb +39 -0
  104. data/spec/lutaml/model/uninitialized_class_spec.rb +14 -2
  105. data/spec/lutaml/model/validation/concerns/has_issues_spec.rb +76 -0
  106. data/spec/lutaml/model/validation/context_spec.rb +60 -0
  107. data/spec/lutaml/model/validation/issue_spec.rb +77 -0
  108. data/spec/lutaml/model/validation/layer_result_spec.rb +66 -0
  109. data/spec/lutaml/model/validation/profile_spec.rb +134 -0
  110. data/spec/lutaml/model/validation/registry_spec.rb +94 -0
  111. data/spec/lutaml/model/validation/remediation_result_spec.rb +23 -0
  112. data/spec/lutaml/model/validation/remediation_spec.rb +72 -0
  113. data/spec/lutaml/model/validation/report_spec.rb +58 -0
  114. data/spec/lutaml/model/validation/rule_spec.rb +134 -0
  115. data/spec/lutaml/model/validation/uninitialized_class_validate_spec.rb +29 -0
  116. data/spec/lutaml/model/validation/validation_error_spec.rb +29 -0
  117. data/spec/lutaml/model/validation/validation_framework_spec.rb +110 -0
  118. data/spec/lutaml/rdf/graph_serialization_spec.rb +137 -0
  119. data/spec/lutaml/rdf/iri_spec.rb +73 -0
  120. data/spec/lutaml/rdf/literal_spec.rb +98 -0
  121. data/spec/lutaml/rdf/mapping_spec.rb +164 -0
  122. data/spec/lutaml/rdf/member_rule_spec.rb +17 -0
  123. data/spec/lutaml/rdf/namespace_set_spec.rb +115 -0
  124. data/spec/lutaml/rdf/namespace_spec.rb +241 -0
  125. data/spec/lutaml/rdf/rdf_transform_spec.rb +82 -0
  126. data/spec/lutaml/turtle/adapter_spec.rb +47 -0
  127. data/spec/lutaml/turtle/mapping_spec.rb +123 -0
  128. data/spec/lutaml/turtle/transform_spec.rb +273 -0
  129. data/spec/lutaml/xml/content_model_validation_spec.rb +157 -0
  130. data/spec/lutaml/xml/mapping_spec.rb +12 -7
  131. metadata +95 -7
  132. data/spec/fixtures/liquid_templates/_ceramics.liquid +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ae1d87545b35bb4c5611ee8063e4ac7529f91b100b4505c7b2f2f8e0815ca07
4
- data.tar.gz: 9c3784b872d6bfed995de44cfcde258c20f6c7004a6e0663332b44c1be653751
3
+ metadata.gz: 2bc036758cf2a4ac1420e14fc6be130730410ab183cd002ced260bdbf08793eb
4
+ data.tar.gz: 1dfc1b473558992eb1481a535d86c1688313951530762c9317af90413028032e
5
5
  SHA512:
6
- metadata.gz: 0130ba36bc10f0fc1791433cd9d7ac7724920f2aa5023042aa4e24480d87cd75f342fc98510fc2dc3da67d67b491c8d0c07ce705819140c6ff989681dbf700e9
7
- data.tar.gz: 0ddc770da4f35d7dffea4219f4663e5cbc56cd19d9ffd8b3396c8b7e7419a20c2491e84dfcc419de71a5b8fc1fbf43abb9d434a4c1d9f4878cc521e672ca65cd
6
+ metadata.gz: 8a4cec37c9acf840b89f6f363aecb8e7bf1a2237d962c6fa8bbe4d4422848cc63812efbd86369780c0e7d26470ff9f220284d062af86b9ebf7f621f3f8e579cb
7
+ data.tar.gz: a48c77e4d99c97fb8cc6fa6793a45b2ba12b79196d9340a23bf4d43a1090c84c05a2402a84ab52792b105713c78caa51f58cfcb141cf79459b06c929adac11b5
@@ -19,6 +19,8 @@ on:
19
19
 
20
20
  jobs:
21
21
  rake:
22
- uses: metanorma/ci/.github/workflows/dependent-rake.yml@main
22
+ uses: metanorma/ci/.github/workflows/dependent-rake.yml@fix/add-pat-token
23
23
  with:
24
24
  command: bundle exec rspec
25
+ secrets:
26
+ pat_token: ${{ secrets.LUTAML_CI_PAT_TOKEN }}
data/.rubocop.yml CHANGED
@@ -24,3 +24,21 @@ Style/OneClassPerFile:
24
24
  - Exclude
25
25
  Exclude:
26
26
  - 'spec/**/*'
27
+
28
+ RSpec/EmptyExampleGroup:
29
+ inherit_mode:
30
+ merge:
31
+ - Exclude
32
+ Exclude:
33
+ - 'spec/lutaml/jsonld/transform_spec.rb'
34
+
35
+ RSpec/NamedSubject:
36
+ inherit_mode:
37
+ merge:
38
+ - Exclude
39
+ Exclude:
40
+ - 'spec/lutaml/turtle/transform_spec.rb'
41
+
42
+ RSpec/ContextMethod:
43
+ Exclude:
44
+ - 'spec/lutaml/jsonld/transform_spec.rb'
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-05-03 22:38:22 UTC using RuboCop version 1.86.1.
3
+ # on 2026-05-06 13:11:11 UTC using RuboCop version 1.86.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -11,15 +11,7 @@ Gemspec/RequiredRubyVersion:
11
11
  Exclude:
12
12
  - 'lutaml-model.gemspec'
13
13
 
14
- # Offense count: 2
15
- # This cop supports safe autocorrection (--autocorrect).
16
- # Configuration parameters: AllowForAlignment.
17
- Layout/CommentIndentation:
18
- Exclude:
19
- - 'lib/lutaml/model/mapping/listener.rb'
20
- - 'lib/lutaml/model/services/base.rb'
21
-
22
- # Offense count: 3210
14
+ # Offense count: 3264
23
15
  # This cop supports safe autocorrection (--autocorrect).
24
16
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
25
17
  # URISchemes: http, https
@@ -118,7 +110,7 @@ Lint/UselessConstantScoping:
118
110
  - 'lib/lutaml/xml/adapter/nokogiri_adapter.rb'
119
111
  - 'lib/lutaml/xml/mapping_rule.rb'
120
112
 
121
- # Offense count: 340
113
+ # Offense count: 355
122
114
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
123
115
  Metrics/AbcSize:
124
116
  Enabled: false
@@ -134,23 +126,23 @@ Metrics/BlockLength:
134
126
  Metrics/BlockNesting:
135
127
  Max: 6
136
128
 
137
- # Offense count: 301
129
+ # Offense count: 315
138
130
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
139
131
  Metrics/CyclomaticComplexity:
140
132
  Enabled: false
141
133
 
142
- # Offense count: 551
134
+ # Offense count: 575
143
135
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
144
136
  Metrics/MethodLength:
145
137
  Max: 514
146
138
 
147
- # Offense count: 85
139
+ # Offense count: 88
148
140
  # Configuration parameters: CountKeywordArgs.
149
141
  Metrics/ParameterLists:
150
142
  Max: 24
151
143
  MaxOptionalParameters: 5
152
144
 
153
- # Offense count: 255
145
+ # Offense count: 264
154
146
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
155
147
  Metrics/PerceivedComplexity:
156
148
  Enabled: false
@@ -242,7 +234,7 @@ RSpec/BeforeAfterAll:
242
234
  RSpec/ContextWording:
243
235
  Enabled: false
244
236
 
245
- # Offense count: 81
237
+ # Offense count: 92
246
238
  # Configuration parameters: IgnoredMetadata.
247
239
  RSpec/DescribeClass:
248
240
  Enabled: false
@@ -253,7 +245,7 @@ RSpec/DescribeMethod:
253
245
  - 'spec/lutaml/xml/schema/xsd/schema_helper_methods_spec.rb'
254
246
  - 'spec/lutaml/xml/serializable_namespace_spec.rb'
255
247
 
256
- # Offense count: 1164
248
+ # Offense count: 1234
257
249
  # Configuration parameters: CountAsOne.
258
250
  RSpec/ExampleLength:
259
251
  Max: 68
@@ -328,11 +320,11 @@ RSpec/MultipleDescribes:
328
320
  - 'spec/lutaml/xml/namespace_resolution_strategy_spec.rb'
329
321
  - 'spec/lutaml/xml/xml_space_type_spec.rb'
330
322
 
331
- # Offense count: 1336
323
+ # Offense count: 1442
332
324
  RSpec/MultipleExpectations:
333
325
  Max: 21
334
326
 
335
- # Offense count: 189
327
+ # Offense count: 190
336
328
  # Configuration parameters: AllowSubject.
337
329
  RSpec/MultipleMemoizedHelpers:
338
330
  Max: 15
@@ -376,13 +368,13 @@ RSpec/RepeatedExampleGroupDescription:
376
368
  Exclude:
377
369
  - 'spec/lutaml/model/mixed_content_spec.rb'
378
370
 
379
- # Offense count: 32
371
+ # Offense count: 40
380
372
  # Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata, InflectorPath, EnforcedInflector.
381
373
  # SupportedInflectors: default, active_support
382
374
  RSpec/SpecFilePathFormat:
383
375
  Enabled: false
384
376
 
385
- # Offense count: 31
377
+ # Offense count: 32
386
378
  # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
387
379
  RSpec/VerifiedDoubles:
388
380
  Exclude:
@@ -393,6 +385,7 @@ RSpec/VerifiedDoubles:
393
385
  - 'spec/lutaml/model/schema/renderer_spec.rb'
394
386
  - 'spec/lutaml/model/transformation_builder_spec.rb'
395
387
  - 'spec/lutaml/model/transformation_spec.rb'
388
+ - 'spec/lutaml/model/validation/context_spec.rb'
396
389
  - 'spec/lutaml/xml/namespace_resolution_strategy_spec.rb'
397
390
 
398
391
  # Offense count: 1
@@ -400,13 +393,14 @@ Security/MarshalLoad:
400
393
  Exclude:
401
394
  - 'scripts-xmi-profile/profile_xmi.rb'
402
395
 
403
- # Offense count: 1
396
+ # Offense count: 2
404
397
  # This cop supports unsafe autocorrection (--autocorrect-all).
405
398
  # Configuration parameters: AllowedMethods, AllowedPatterns.
406
399
  # AllowedMethods: ==, equal?, eql?
407
400
  Style/ClassEqualityComparison:
408
401
  Exclude:
409
402
  - 'lib/lutaml/model/attribute.rb'
403
+ - 'lib/lutaml/model/validation/profile.rb'
410
404
 
411
405
  # Offense count: 13
412
406
  # This cop supports safe autocorrection (--autocorrect).
data/Gemfile CHANGED
@@ -12,6 +12,7 @@ gem "base64"
12
12
  gem "benchmark-ips"
13
13
  gem "bigdecimal"
14
14
  gem "canon" # , path: "../canon"
15
+ gem "json-ld", "~> 3.3"
15
16
  gem "liquid", "~> 5"
16
17
  gem "multi_json"
17
18
  gem "nokogiri"
@@ -20,6 +21,7 @@ gem "oj"
20
21
  gem "openssl", "~> 3.0"
21
22
  gem "ox"
22
23
  gem "rake"
24
+ gem "rdf-turtle", "~> 3.3"
23
25
  gem "rexml"
24
26
  gem "rspec"
25
27
  gem "rubocop"
data/README.adoc CHANGED
@@ -27,7 +27,7 @@ for:
27
27
 
28
28
  It provides simple, flexible and comprehensive mechanisms for defining
29
29
  information models with attributes and types, and the serialization of them
30
- to/from serialization formats including JSON, XML, YAML, and TOML, as well as
30
+ to/from serialization formats including JSON, XML, YAML, TOML, JSON-LD, and Turtle, as well as
31
31
  transformation to other formats like Hash.
32
32
 
33
33
  For serialization formats, it uses an adapter pattern to support multiple
@@ -48,7 +48,7 @@ link:docs/migration-guides/0-1-0-migrate-from-shale.adoc[Migrating from Shale to
48
48
  == Features
49
49
 
50
50
  * Define models with attributes and types
51
- * Serialize and deserialize models to/from JSON, XML, YAML, and TOML
51
+ * Serialize and deserialize models to/from JSON, XML, YAML, TOML, JSON-LD, and Turtle
52
52
  * Transform models to other formats like Hash
53
53
  * Support for multiple serialization libraries (e.g., `toml-rb`, `tomlib`)
54
54
  * Configurable adapters for different serialization formats
@@ -65,7 +65,9 @@ link:docs/migration-guides/0-1-0-migrate-from-shale.adoc[Migrating from Shale to
65
65
  * Symmetric OOP architecture for all formats with KeyValueDataModel (see <<keyvaluedatamodel-architecture>>)
66
66
  * Parse XSD schemas into Ruby objects for inspection and manipulation (see <<xsd-schema-parsing>>)
67
67
  * Reusable XML mapping classes for sharing mappings across models
68
+ * RDF namespace classes and JSON-LD/Turtle format adapters for Linked Data serialization
68
69
  * Consolidation mapping: group sibling XML elements into structured model instances (see <<consolidation-mapping>>)
70
+ * Document-level validation framework with composable rules, profiles, and remediation (see <<document-validation-framework>>)
69
71
 
70
72
 
71
73
  == Data modeling in a nutshell
@@ -5390,6 +5392,11 @@ Collection serialization formats::
5390
5392
  `jsonl`::: JSONL (JSON Lines) (see <<mapping-collections>>)
5391
5393
  `yamls`::: YAML Stream (multi-document format) (see <<mapping-collections>>)
5392
5394
 
5395
+ RDF serialization formats::
5396
+
5397
+ `rdf`::: Unified RDF mapping for both JSON-LD and Turtle (see <<mapping-rdf>>)
5398
+ `turtle`::: Turtle (see link:docs/_guides/turtle-serialization.adoc[Turtle Serialization])
5399
+
5393
5400
 
5394
5401
  .Using the `xml`, `hsh`, `json`, `yaml`, `toml` and `key_value` blocks to define serialization mappings
5395
5402
  [example]
@@ -5438,6 +5445,73 @@ end
5438
5445
  ====
5439
5446
 
5440
5447
 
5448
+ [[mapping-rdf]]
5449
+ === Unified RDF mapping (`rdf`)
5450
+
5451
+ The `rdf` block defines predicate-based mappings once for both JSON-LD and
5452
+ Turtle serialization. It follows the same unified-mapping principle as
5453
+ `key_value` (which serves JSON, YAML, TOML).
5454
+
5455
+ .Using the `rdf` block to define unified RDF mappings
5456
+ [example]
5457
+ ====
5458
+ [source,ruby]
5459
+ ----
5460
+ class Concept < Lutaml::Model::Serializable
5461
+ attribute :code, :string
5462
+ attribute :name, :string
5463
+
5464
+ rdf do
5465
+ namespace SkosNamespace
5466
+
5467
+ subject { |m| "http://example.org/concept/#{m.code}" }
5468
+ type "skos:Concept"
5469
+
5470
+ predicate :notation, namespace: SkosNamespace, to: :code
5471
+ predicate :prefLabel, namespace: SkosNamespace, to: :name
5472
+ end
5473
+ end
5474
+ ----
5475
+
5476
+ Serializes to both formats:
5477
+
5478
+ [source,ruby]
5479
+ ----
5480
+ concept = Concept.new(code: "2119", name: "component")
5481
+ concept.to_turtle # => Turtle with @prefix, a skos:Concept, predicates
5482
+ concept.to_jsonld # => JSON-LD with @context, @type, @id, properties
5483
+ ----
5484
+ ====
5485
+
5486
+ Graph-level serialization uses `members` to emit all contained resources as
5487
+ separate subjects in the same document:
5488
+
5489
+ [source,ruby]
5490
+ ----
5491
+ class Vocabulary < Lutaml::Model::Serializable
5492
+ attribute :id, :string
5493
+ attribute :concepts, Concept, collection: true
5494
+
5495
+ rdf do
5496
+ namespace SkosNamespace
5497
+ subject { |v| "http://example.org/vocab/#{v.id}" }
5498
+ type "skos:ConceptScheme"
5499
+ predicate :prefLabel, namespace: SkosNamespace, to: :id
5500
+ members :concepts
5501
+ end
5502
+ end
5503
+
5504
+ # Turtle: single document with ConceptScheme + all Concept triples
5505
+ # JSON-LD: @graph array with ConceptScheme + all Concept objects
5506
+ vocab.to_turtle
5507
+ vocab.to_jsonld
5508
+ ----
5509
+
5510
+ See link:docs/_guides/rdf-serialization.adoc[Unified RDF Serialization] for the
5511
+ complete guide including language-tagged values, graph serialization, and
5512
+ architecture details.
5513
+
5514
+
5441
5515
  == Serialization: XML
5442
5516
 
5443
5517
  === General
@@ -6349,7 +6423,8 @@ end
6349
6423
  ====
6350
6424
  When a model has `mixed_content`, use `map_content` with `collection: true`
6351
6425
  to capture the multiple text segments between child elements.
6352
- Without `collection: true`, only the first (or last) text segment is captured.
6426
+ Without `collection: true`, a `MixedContentCollectionError` is raised at
6427
+ finalization time.
6353
6428
  ====
6354
6429
 
6355
6430
  .Complete round-trip with `mixed_content` and `map_content collection: true`
@@ -6498,6 +6573,51 @@ Without `mixed_content`, serialization groups all text first then all elements
6498
6573
  (or vice versa), losing the original interleaving. This is the most common
6499
6574
  cause of XML round-trip failures in document-processing applications.
6500
6575
 
6576
+ ===== XML Comment round-trip preservation
6577
+
6578
+ XML comments (`<!-- ... -->`) are preserved during round-trip serialization
6579
+ in `mixed_content` and `ordered` modes. Comments appear in `element_order`
6580
+ as entries with `node_type: :comment` and are reconstructed during output.
6581
+
6582
+ .Comment round-trip in mixed content
6583
+ [example]
6584
+ ====
6585
+ [source,ruby]
6586
+ ----
6587
+ class Paragraph < Lutaml::Model::Serializable
6588
+ attribute :content, :string, collection: true
6589
+
6590
+ xml do
6591
+ element 'p'
6592
+ mixed_content
6593
+ map_content to: :content
6594
+ end
6595
+ end
6596
+ ----
6597
+
6598
+ Input XML with a comment between text and an element:
6599
+
6600
+ [source,xml]
6601
+ ----
6602
+ <p>Text before<!-- a comment --> text after</p>
6603
+ ----
6604
+
6605
+ [source,ruby]
6606
+ ----
6607
+ parsed = Paragraph.from_xml(xml)
6608
+ parsed.element_order
6609
+ # => [#<Lutaml::Xml::Element type: "Text", node_type: :text>,
6610
+ # #<Lutaml::Xml::Element type: "Comment", node_type: :comment, text_content: " a comment ">,
6611
+ # #<Lutaml::Xml::Element type: "Text", node_type: :text>]
6612
+
6613
+ parsed.to_xml
6614
+ # => "<p>Text before<!-- a comment --> text after</p>"
6615
+ ----
6616
+ ====
6617
+
6618
+ NOTE: Comments are tracked in `element_order` but are **not** included in
6619
+ model attributes or the parsed hash. They exist solely for serialization fidelity.
6620
+
6501
6621
  ==== (DEPRECATED) Mixed content declaration (`root` method with `mixed:`)
6502
6622
 
6503
6623
  To map this to Lutaml::Model we can use the `mixed` option in either way:
@@ -6558,6 +6678,13 @@ preserves the order of **XML Elements and Content**.
6558
6678
 
6559
6679
  NOTE: When both options are used, `mixed: true` takes precedence.
6560
6680
 
6681
+ [IMPORTANT]
6682
+ ====
6683
+ `ordered` models element-only content -- `map_content` is not allowed.
6684
+ If you need to capture text content between elements, use `mixed_content`
6685
+ instead (see <<mixed-content>>).
6686
+ ====
6687
+
6561
6688
  To specify ordered content, the `ordered: true` option needs to be set at the
6562
6689
  `xml` block's `root` method.
6563
6690
 
@@ -10046,8 +10173,14 @@ Key-value data models supported are identified by their short name:
10046
10173
  `json`:: JSON
10047
10174
  `yaml`:: YAML
10048
10175
  `toml`:: TOML
10176
+ `jsonld`:: JSON-LD (extends key-value with `@context`, `@type`, `@id`)
10049
10177
  `key_value`:: A way to configure key-value mappings for all supported key-value data models.
10050
10178
 
10179
+ RDF serialization formats::
10180
+
10181
+ `rdf`:: Unified RDF mapping for both JSON-LD and Turtle (see <<mapping-rdf>>)
10182
+ `turtle`:: Turtle (see link:docs/_guides/turtle-serialization.adoc[Turtle Serialization])
10183
+
10051
10184
 
10052
10185
  === Mapping
10053
10186
 
@@ -15681,6 +15814,8 @@ LutaML, out of the box, supports the following serialization formats:
15681
15814
  * YAML (https://yaml.org/[YAML version 1.2])
15682
15815
  * JSON (https://www.ecma-international.org/publications-and-standards/standards/ecma-404/[ECMA-404 The JSON Data Interchange Standard], unofficial link: https://www.json.org[JSON])
15683
15816
  * TOML (https://toml.io/en[TOML version 1.0])
15817
+ * JSON-LD (https://www.w3.org/TR/json-ld11/[W3C JSON-LD 1.1])
15818
+ * Turtle (https://www.w3.org/TR/turtle/[W3C RDF 1.1 Turtle])
15684
15819
 
15685
15820
  The adapter interface is also used to support certain transformation of models
15686
15821
  into an "end format", which is not a serialization format. For example, the
@@ -15923,6 +16058,37 @@ end
15923
16058
  ----
15924
16059
 
15925
16060
 
16061
+ === JSON-LD
16062
+
16063
+ Lutaml::Model supports serialization to and from JSON-LD (W3C JSON-LD 1.1).
16064
+ JSON-LD is a key-value format that extends JSON with `@context`, `@type`, and
16065
+ `@id`. No additional gem is required beyond `lutaml-model`.
16066
+
16067
+ For unified RDF mapping (both JSON-LD and Turtle from a single `rdf` block), see
16068
+ link:docs/_guides/rdf-serialization.adoc[Unified RDF Serialization].
16069
+
16070
+ See link:docs/_guides/jsonld-serialization.adoc[JSON-LD Serialization] for the
16071
+ legacy `jsonld do` block DSL and usage examples.
16072
+
16073
+
16074
+ === Turtle
16075
+
16076
+ Lutaml::Model supports serialization to and from W3C RDF Turtle format.
16077
+ Turtle is a non-key-value format based on RDF triples. Requires the
16078
+ `rdf-turtle` gem:
16079
+
16080
+ [source,ruby]
16081
+ ----
16082
+ gem "rdf-turtle", "~> 3.3"
16083
+ ----
16084
+
16085
+ For unified RDF mapping (both JSON-LD and Turtle from a single `rdf` block), see
16086
+ link:docs/_guides/rdf-serialization.adoc[Unified RDF Serialization].
16087
+
16088
+ See link:docs/_guides/turtle-serialization.adoc[Turtle Serialization] for the
16089
+ legacy `turtle do` block DSL and usage examples.
16090
+
16091
+
15926
16092
  === Per-operation adapter override
15927
16093
 
15928
16094
  Override the adapter for a single serialization/deserialization call using the `adapter:` option:
@@ -16938,6 +17104,164 @@ end
16938
17104
  For a complete architecture overview and migration guide from Register to
16939
17105
  GlobalContext, see link:docs/architecture.md[Architecture Documentation].
16940
17106
 
17107
+ [[document-validation-framework]]
17108
+ == Document Validation Framework
17109
+
17110
+ Lutaml::Model provides a document-level validation framework (`Lutaml::Model::Validation`)
17111
+ for validating structural integrity, cross-references, and conformance against
17112
+ domain-specific rules. This is *orthogonal* to the existing attribute-level
17113
+ validation (`validate`/`validate!` on model instances) -- that validates type
17114
+ constraints, enumerations, and collections; this validates document-level concerns.
17115
+
17116
+ === Quick start
17117
+
17118
+ [source,ruby]
17119
+ ----
17120
+ require "lutaml/model/validation_framework"
17121
+
17122
+ # 1. Define rules by subclassing Lutaml::Model::Validation::Rule
17123
+ class RequiredFieldsRule < Lutaml::Model::Validation::Rule
17124
+ def code = "DOC-001"
17125
+ def severity = "error"
17126
+
17127
+ def check(context)
17128
+ missing = required_fields.select { |f| context[f].nil? || context[f].empty? }
17129
+ missing.map { |f| issue("Missing required field: #{f}") }
17130
+ end
17131
+
17132
+ private
17133
+
17134
+ def required_fields = %w[title author]
17135
+ end
17136
+
17137
+ # 2. Register rules
17138
+ registry = Lutaml::Model::Validation.new_registry
17139
+ registry.register(RequiredFieldsRule)
17140
+
17141
+ # 3. Run validation
17142
+ issues = Lutaml::Model::Validation.validate({ title: "Hello" }, registry)
17143
+ issues.each { |i| puts "[#{i.severity}] #{i.code}: #{i.message}" }
17144
+ # => [error] DOC-001: Missing required field: author
17145
+
17146
+ # 4. Or raise on errors
17147
+ begin
17148
+ Lutaml::Model::Validation.validate!({ title: "Hello" }, registry)
17149
+ rescue Lutaml::Model::Validation::ValidationError => e
17150
+ puts e.message # => "[DOC-001] Missing required field: author"
17151
+ e.issues # => [#<Issue code="DOC-001">]
17152
+ end
17153
+ ----
17154
+
17155
+ === Core components
17156
+
17157
+ [cols="1,4"]
17158
+ |===
17159
+ | Class | Purpose
17160
+
17161
+ | `Validation::Issue`
17162
+ | Serializable issue with severity (`error`/`warning`/`info`/`notice`), code, message, location, line, suggestion
17163
+
17164
+ | `Validation::Rule`
17165
+ | Abstract base class. Override `code`, `severity`, `category`, `applicable?`, and `check`
17166
+
17167
+ | `Validation::Registry`
17168
+ | Instance-based rule registration with cached instantiation via `register`, `all`, `for_category`, `find`
17169
+
17170
+ | `Validation::Profile`
17171
+ | YAML composable profiles with import resolution for rule subsets. `load` validates required `name` key
17172
+
17173
+ | `Validation::Context`
17174
+ | Mutable context with error accumulation and per-rule state (for streaming validation)
17175
+
17176
+ | `Validation::Report` / `LayerResult`
17177
+ | Serializable report models with aggregated severity filtering
17178
+
17179
+ | `Validation::Remediation`
17180
+ | Abstract base for auto-fix logic. Subclass must override `fix` (raises `NotImplementedError` on base class)
17181
+ |===
17182
+
17183
+ === Composable profiles
17184
+
17185
+ Profiles select *which* rules run, not *how* they run. They are loaded from YAML
17186
+ and support import-based composition.
17187
+
17188
+ [source,yaml]
17189
+ ----
17190
+ # profiles/basic.yml
17191
+ name: basic
17192
+ description: Basic DOCX validation
17193
+ rules:
17194
+ - RequiredFieldsRule
17195
+ - StyleReferencesRule
17196
+ ----
17197
+
17198
+ [source,yaml]
17199
+ ----
17200
+ # profiles/strict.yml
17201
+ name: strict
17202
+ import:
17203
+ - basic
17204
+ rules:
17205
+ - BookmarksRule
17206
+ - ImagesRule
17207
+ ----
17208
+
17209
+ [source,ruby]
17210
+ ----
17211
+ registry = Lutaml::Model::Validation.new_registry
17212
+ registry.register(RequiredFieldsRule)
17213
+ registry.register(StyleReferencesRule)
17214
+ registry.register(BookmarksRule)
17215
+ registry.register(ImagesRule)
17216
+
17217
+ basic = Lutaml::Model::Validation::Profile.load("profiles/basic.yml")
17218
+ strict = Lutaml::Model::Validation::Profile.load("profiles/strict.yml")
17219
+ profiles = { "basic" => basic, "strict" => strict }
17220
+
17221
+ rules = strict.resolve(registry, profiles) # returns all 4 rule instances
17222
+
17223
+ // Circular imports are detected and raise ArgumentError.
17224
+ // Profile YAML is validated: missing `name` key raises ArgumentError.
17225
+ ----
17226
+
17227
+ === Remediation (auto-fix)
17228
+
17229
+ Subclass `Validation::Remediation` to provide automatic fixes for specific issues:
17230
+
17231
+ [source,ruby]
17232
+ ----
17233
+ class FixBrokenReferences < Lutaml::Model::Validation::Remediation
17234
+ def id = "REM-001"
17235
+ def targets = ["DOC-020"]
17236
+
17237
+ def fix(context, report)
17238
+ # ... apply fixes ...
17239
+ Lutaml::Model::Validation::RemediationResult.new(
17240
+ success: true,
17241
+ message: "Fixed 3 broken references",
17242
+ fixed_codes: ["DOC-020"]
17243
+ )
17244
+ end
17245
+ end
17246
+ ----
17247
+
17248
+ === Relationship to attribute-level validation
17249
+
17250
+ |===
17251
+ | Attribute validation (existing) | Document validation framework (new)
17252
+
17253
+ | Validates model instance state (types, ranges, patterns)
17254
+ | Validates document-level concerns (structure, cross-references, conformance)
17255
+
17256
+ | Runs during deserialization via `model.validate`
17257
+ | Runs on parsed documents against domain rules via `Validation.validate`
17258
+
17259
+ | Returns `Array<Error>` error objects
17260
+ | Returns `Array<Issue>` serializable issue objects
17261
+ |===
17262
+
17263
+ See link:docs/_pages/validation.adoc[Validation] for attribute-level validation details.
17264
+
16941
17265
 
16942
17266
  == Comparison with Shale
16943
17267