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.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +0 -3
- data/.rubocop_todo.yml +49 -52
- data/README.adoc +188 -25
- data/docs/_guides/xml/namespace-semantics.adoc +2 -0
- data/docs/_guides/xml-mapping.adoc +178 -24
- data/docs/_guides/xml-namespace-qualification.adoc +142 -0
- data/docs/_pages/importable_models.adoc +7 -1
- data/docs/_tutorials/xml-element-attribute-namespace-guide.adoc +2 -0
- data/docs/_tutorials/xml-schema-primer-style-guide.adoc +2 -0
- data/docs/namespace-management.adoc +2 -0
- data/docs/xml-schema-qualification.md +6 -0
- data/lib/lutaml/jsonld.rb +1 -4
- data/lib/lutaml/model/attribute.rb +4 -0
- data/lib/lutaml/model/choice.rb +34 -0
- data/lib/lutaml/model/compiled_rule.rb +7 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model.rb +3 -1
- data/lib/lutaml/{jsonld → rdf}/context.rb +1 -1
- data/lib/lutaml/{jsonld/transform.rb → rdf/linked_data_transform.rb} +33 -35
- data/lib/lutaml/{jsonld → rdf}/term_definition.rb +1 -1
- data/lib/lutaml/rdf.rb +3 -0
- 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/adapter_element.rb +2 -2
- 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 +39 -26
- data/lib/lutaml/xml/transformation/rule_applier.rb +21 -0
- data/lib/lutaml/xml/transformation/rule_compiler.rb +12 -3
- data/lib/lutaml/yamlld/adapter.rb +25 -0
- data/lib/lutaml/yamlld.rb +25 -0
- data/spec/lutaml/integration/multi_format_spec.rb +23 -0
- data/spec/lutaml/model/attribute_spec.rb +8 -1
- data/spec/lutaml/model/choice_restrict_spec.rb +225 -0
- data/spec/lutaml/model/custom_model_spec.rb +4 -4
- data/spec/lutaml/model/mixed_content_spec.rb +50 -9
- data/spec/lutaml/model/multiple_mapping_spec.rb +4 -4
- data/spec/lutaml/model/ordered_content_spec.rb +3 -3
- data/spec/lutaml/model/raw_element_spec.rb +533 -0
- data/spec/lutaml/model/uninitialized_class_spec.rb +1 -1
- data/spec/lutaml/model/xsd_form_default_patterns_spec.rb +2 -2
- data/spec/lutaml/model/xsd_patterns_spec.rb +4 -4
- data/spec/lutaml/{jsonld → rdf}/context_spec.rb +2 -2
- data/spec/lutaml/{jsonld/transform_spec.rb → rdf/linked_data_transform_spec.rb} +18 -2
- data/spec/lutaml/rdf/mapping_spec.rb +2 -1
- data/spec/lutaml/{jsonld → rdf}/term_definition_spec.rb +2 -2
- data/spec/lutaml/turtle/transform_spec.rb +2 -2
- data/spec/lutaml/xml/enhanced_mapping_spec.rb +230 -1
- data/spec/lutaml/xml/mapping_spec.rb +4 -4
- data/spec/lutaml/xml/namespace_placement_spec.rb +11 -8
- data/spec/lutaml/xml/namespace_three_phase_spec.rb +1 -1
- data/spec/lutaml/xml/prefix_control_spec.rb +4 -4
- data/spec/lutaml/xml/serializable_namespace_spec.rb +6 -6
- data/spec/lutaml/xml/type_namespace_examples_spec.rb +1 -1
- data/spec/lutaml/xml/type_namespace_integration_spec.rb +3 -3
- data/spec/lutaml/yamlld/adapter_spec.rb +56 -0
- data/spec/lutaml/yamlld/registration_spec.rb +33 -0
- metadata +14 -8
|
@@ -1097,42 +1097,184 @@ end
|
|
|
1097
1097
|
====
|
|
1098
1098
|
|
|
1099
1099
|
|
|
1100
|
-
[[xml-map-all]]
|
|
1101
|
-
==== Mapping entire XML element into an attribute
|
|
1102
1100
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1101
|
+
[[xml-raw-element]]
|
|
1102
|
+
==== Capturing raw XML content
|
|
1105
1103
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1104
|
+
Lutaml::Model provides several mechanisms for capturing XML content as raw strings.
|
|
1105
|
+
Use these when you need to embed foreign XML vocabularies (SVG, MathML, XSL-FO) or
|
|
1106
|
+
preserve markup that your model doesn't need to parse.
|
|
1108
1107
|
|
|
1109
|
-
|
|
1108
|
+
===== `raw: :element` on `map_element` -- full element capture (recommended)
|
|
1110
1109
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1110
|
+
The `raw: :element` option captures a specific mapped element as a complete XML
|
|
1111
|
+
string, including its opening tag, attributes, children, and closing tag.
|
|
1112
|
+
This provides **lossless round-trip** fidelity -- the captured string is
|
|
1113
|
+
self-contained and serialized verbatim.
|
|
1114
1114
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1115
|
+
.Syntax
|
|
1116
|
+
[source,ruby]
|
|
1117
|
+
----
|
|
1118
|
+
xml do
|
|
1119
|
+
map_element "svg", to: :svg_data, raw: :element
|
|
1120
|
+
end
|
|
1121
|
+
----
|
|
1117
1122
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1123
|
+
.Capturing an SVG element as raw XML
|
|
1124
|
+
[example]
|
|
1125
|
+
====
|
|
1126
|
+
[source,ruby]
|
|
1127
|
+
----
|
|
1128
|
+
class SvgContainer < Lutaml::Model::Serializable
|
|
1129
|
+
attribute :name, :string
|
|
1130
|
+
attribute :svg_data, :string
|
|
1120
1131
|
|
|
1121
|
-
|
|
1132
|
+
xml do
|
|
1133
|
+
element "container"
|
|
1134
|
+
map_element "name", to: :name
|
|
1135
|
+
map_element "svg", to: :svg_data, raw: :element
|
|
1136
|
+
end
|
|
1137
|
+
end
|
|
1138
|
+
----
|
|
1122
1139
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1140
|
+
[source,xml]
|
|
1141
|
+
----
|
|
1142
|
+
<container>
|
|
1143
|
+
<name>diagram</name>
|
|
1144
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
|
1145
|
+
<rect x="0" y="0" width="100" height="100" fill="red"/>
|
|
1146
|
+
</svg>
|
|
1147
|
+
</container>
|
|
1148
|
+
----
|
|
1126
1149
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1150
|
+
[source,ruby]
|
|
1151
|
+
----
|
|
1152
|
+
> doc = SvgContainer.from_xml(xml)
|
|
1153
|
+
> doc.name
|
|
1154
|
+
# => "diagram"
|
|
1155
|
+
> doc.svg_data
|
|
1156
|
+
# => "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\">\n <rect x=\"0\" y=\"0\" width=\"100\" height=\"100\" fill=\"red\"/>\n</svg>"
|
|
1157
|
+
----
|
|
1130
1158
|
|
|
1131
|
-
|
|
1132
|
-
the
|
|
1159
|
+
The captured string round-trips correctly -- serializing the model back to XML
|
|
1160
|
+
outputs the raw element verbatim without escaping or wrapping.
|
|
1133
1161
|
|
|
1134
|
-
|
|
1162
|
+
`raw: :element` also works with collection attributes:
|
|
1135
1163
|
|
|
1164
|
+
[source,ruby]
|
|
1165
|
+
----
|
|
1166
|
+
class MultiFragment < Lutaml::Model::Serializable
|
|
1167
|
+
attribute :fragments, :string, collection: true
|
|
1168
|
+
|
|
1169
|
+
xml do
|
|
1170
|
+
element "container"
|
|
1171
|
+
map_element "fragment", to: :fragments, raw: :element
|
|
1172
|
+
end
|
|
1173
|
+
end
|
|
1174
|
+
----
|
|
1175
|
+
====
|
|
1176
|
+
|
|
1177
|
+
===== `raw: :content` on `map_element` -- inner content capture
|
|
1178
|
+
|
|
1179
|
+
The `raw: :content` option captures only the *inner XML* of the matched element,
|
|
1180
|
+
without the wrapper tags. The wrapper element is reconstructed from the mapping rule
|
|
1181
|
+
during serialization.
|
|
1182
|
+
|
|
1183
|
+
.Syntax
|
|
1184
|
+
[source,ruby]
|
|
1185
|
+
----
|
|
1186
|
+
xml do
|
|
1187
|
+
map_element "street", to: :street, raw: :content
|
|
1188
|
+
end
|
|
1189
|
+
----
|
|
1190
|
+
|
|
1191
|
+
.Capturing inner XML content
|
|
1192
|
+
[example]
|
|
1193
|
+
====
|
|
1194
|
+
[source,ruby]
|
|
1195
|
+
----
|
|
1196
|
+
class Address < Lutaml::Model::Serializable
|
|
1197
|
+
attribute :street, :string
|
|
1198
|
+
|
|
1199
|
+
xml do
|
|
1200
|
+
element "address"
|
|
1201
|
+
map_element "street", to: :street, raw: :content
|
|
1202
|
+
end
|
|
1203
|
+
end
|
|
1204
|
+
----
|
|
1205
|
+
|
|
1206
|
+
[source,xml]
|
|
1207
|
+
----
|
|
1208
|
+
<address><street><b>123</b> Main St</street></address>
|
|
1209
|
+
----
|
|
1210
|
+
|
|
1211
|
+
[source,ruby]
|
|
1212
|
+
----
|
|
1213
|
+
> doc = Address.from_xml(xml)
|
|
1214
|
+
> doc.street
|
|
1215
|
+
# => "<b>123</b> Main St"
|
|
1216
|
+
----
|
|
1217
|
+
====
|
|
1218
|
+
|
|
1219
|
+
NOTE: `raw: :content` is **lossy** -- namespace prefixes declared on the wrapper
|
|
1220
|
+
element are not captured. For lossless capture, use `raw: :element` instead.
|
|
1221
|
+
|
|
1222
|
+
===== Namespace behavior
|
|
1223
|
+
|
|
1224
|
+
Both `raw: :element` and `raw: :content` match elements by local name regardless
|
|
1225
|
+
of namespace or prefix:
|
|
1226
|
+
|
|
1227
|
+
* Elements with no namespace (`<svg>`)
|
|
1228
|
+
* Elements with an `xmlns` declaration on themselves (`<svg xmlns="...">`)
|
|
1229
|
+
* Elements inheriting a default namespace from a parent
|
|
1230
|
+
* Elements with an explicit namespace prefix (`<ns:svg>`)
|
|
1231
|
+
|
|
1232
|
+
This is intentional -- raw capture is designed to handle foreign XML vocabularies
|
|
1233
|
+
without needing model classes for them.
|
|
1234
|
+
|
|
1235
|
+
===== Round-trip fidelity comparison
|
|
1236
|
+
|
|
1237
|
+
| Aspect | `raw: :element` | `raw: :content` |
|
|
1238
|
+
|--------|----------------|-----------------|
|
|
1239
|
+
| Element name | Preserved (in string) | Reconstructed from rule |
|
|
1240
|
+
| Element attributes | Preserved (in string) | Lost unless separately mapped |
|
|
1241
|
+
| Inner content | Preserved (in string) | Preserved (in string) |
|
|
1242
|
+
| Namespace prefixes | Safe (declared in string) | May break if declared on wrapper |
|
|
1243
|
+
| Fidelity | **Lossless** | **Lossy** |
|
|
1244
|
+
|
|
1245
|
+
===== Wrapper model pattern for attribute extraction
|
|
1246
|
+
|
|
1247
|
+
Raw capture stores the element as a string. If you need to inspect or modify
|
|
1248
|
+
attributes on the captured element, use a wrapper model with `map_all`:
|
|
1249
|
+
|
|
1250
|
+
[source,ruby]
|
|
1251
|
+
----
|
|
1252
|
+
class SvgInner < Lutaml::Model::Serializable
|
|
1253
|
+
attribute :raw, :string
|
|
1254
|
+
|
|
1255
|
+
xml do
|
|
1256
|
+
element "svg"
|
|
1257
|
+
map_all to: :raw
|
|
1258
|
+
end
|
|
1259
|
+
end
|
|
1260
|
+
|
|
1261
|
+
class Container < Lutaml::Model::Serializable
|
|
1262
|
+
attribute :svg_data, SvgInner
|
|
1263
|
+
|
|
1264
|
+
xml do
|
|
1265
|
+
element "container"
|
|
1266
|
+
map_element "svg", to: :svg_data
|
|
1267
|
+
end
|
|
1268
|
+
end
|
|
1269
|
+
----
|
|
1270
|
+
|
|
1271
|
+
===== `map_all` -- root inner XML capture
|
|
1272
|
+
|
|
1273
|
+
The `map_all` directive captures the entire inner XML of the root element into
|
|
1274
|
+
a single attribute. It is **exclusive** -- it cannot be combined with other
|
|
1275
|
+
`map_element` or `map_content` mappings (only `map_attribute` is allowed).
|
|
1276
|
+
|
|
1277
|
+
.Syntax
|
|
1136
1278
|
[source,ruby]
|
|
1137
1279
|
----
|
|
1138
1280
|
xml do
|
|
@@ -1167,6 +1309,18 @@ end
|
|
|
1167
1309
|
----
|
|
1168
1310
|
====
|
|
1169
1311
|
|
|
1312
|
+
===== Deprecated: `attribute :x, :string, raw: true`
|
|
1313
|
+
|
|
1314
|
+
[WARNING]
|
|
1315
|
+
====
|
|
1316
|
+
`attribute :name, :string, raw: true` is deprecated. Use
|
|
1317
|
+
`map_element "name", to: :name, raw: :content` instead.
|
|
1318
|
+
====
|
|
1319
|
+
|
|
1320
|
+
This legacy syntax captures the inner XML of the matched element.
|
|
1321
|
+
It is equivalent to `raw: :content` on the mapping. Existing code continues
|
|
1322
|
+
to work but emits a deprecation warning.
|
|
1323
|
+
|
|
1170
1324
|
|
|
1171
1325
|
==== Mapping CDATA nodes
|
|
1172
1326
|
|
|
@@ -509,6 +509,148 @@ Parent.new(child: Child.new(value: "test")).to_xml(prefix: true)
|
|
|
509
509
|
|
|
510
510
|
All three elements (`parent`, `child`, `value`) use the prefix because all are qualified via `element_form_default: :qualified`.
|
|
511
511
|
|
|
512
|
+
=== Form Override on Serializable Children
|
|
513
|
+
|
|
514
|
+
The `form:` option works on attributes of any type — including attributes whose value type is another `Serializable` model, not only simple types like `:string`. This matters when the parent's namespace declares `element_form_default :unqualified` (the W3C default) and you need a specific nested model element to be qualified anyway.
|
|
515
|
+
|
|
516
|
+
.Worked example: form: :qualified on a Serializable child
|
|
517
|
+
[example]
|
|
518
|
+
====
|
|
519
|
+
[source,ruby]
|
|
520
|
+
----
|
|
521
|
+
NS = Class.new(Lutaml::Model::XmlNamespace) do
|
|
522
|
+
uri "https://example.com/ns"
|
|
523
|
+
prefix_default "ex"
|
|
524
|
+
element_form_default :unqualified # W3C default for local elements
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
class Child < Lutaml::Model::Serializable
|
|
528
|
+
attribute :label, :string
|
|
529
|
+
xml do
|
|
530
|
+
element "child"
|
|
531
|
+
namespace NS
|
|
532
|
+
map_element "label", to: :label
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
class Parent < Lutaml::Model::Serializable
|
|
537
|
+
attribute :child, Child
|
|
538
|
+
xml do
|
|
539
|
+
element "item"
|
|
540
|
+
namespace NS
|
|
541
|
+
map_element "child", to: :child, form: :qualified # Force prefix
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
Parent.new(child: Child.new(label: "x")).to_xml
|
|
546
|
+
----
|
|
547
|
+
|
|
548
|
+
*Output*:
|
|
549
|
+
[source,xml]
|
|
550
|
+
----
|
|
551
|
+
<item xmlns="https://example.com/ns" xmlns:ex="https://example.com/ns">
|
|
552
|
+
<ex:child>
|
|
553
|
+
<label>x</label>
|
|
554
|
+
</ex:child>
|
|
555
|
+
</item>
|
|
556
|
+
----
|
|
557
|
+
|
|
558
|
+
The `ex:` prefix on `<child>` is forced by `form: :qualified`, overriding the parent's `:unqualified` schema default. The inner `<label>` remains unprefixed because the `Child` model's own mapping rule for `label` has no `form:` override — see the next section.
|
|
559
|
+
====
|
|
560
|
+
|
|
561
|
+
=== Form Scope: Per-Rule, Not Transitive
|
|
562
|
+
|
|
563
|
+
`form:` is a per-mapping-rule override. It does **not** propagate transitively to grandchildren. Each level of the model tree applies its own rule against its own parent's `element_form_default`.
|
|
564
|
+
|
|
565
|
+
.Worked example: form on parent does not cascade to grandchild
|
|
566
|
+
[example]
|
|
567
|
+
====
|
|
568
|
+
[source,ruby]
|
|
569
|
+
----
|
|
570
|
+
NS = Class.new(Lutaml::Model::XmlNamespace) do
|
|
571
|
+
uri "https://example.com/ns"
|
|
572
|
+
prefix_default "ex"
|
|
573
|
+
element_form_default :unqualified
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
class Grandchild < Lutaml::Model::Serializable
|
|
577
|
+
attribute :value, :string
|
|
578
|
+
xml do
|
|
579
|
+
element "grandchild"
|
|
580
|
+
namespace NS
|
|
581
|
+
map_element "value", to: :value
|
|
582
|
+
end
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
class Child < Lutaml::Model::Serializable
|
|
586
|
+
attribute :inner, Grandchild
|
|
587
|
+
xml do
|
|
588
|
+
element "child"
|
|
589
|
+
namespace NS
|
|
590
|
+
map_element "grandchild", to: :inner # NO form: override
|
|
591
|
+
end
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
class Parent < Lutaml::Model::Serializable
|
|
595
|
+
attribute :child, Child
|
|
596
|
+
xml do
|
|
597
|
+
element "item"
|
|
598
|
+
namespace NS
|
|
599
|
+
map_element "child", to: :child, form: :qualified # Parent's rule only
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
Parent.new(child: Child.new(inner: Grandchild.new(value: "x"))).to_xml
|
|
604
|
+
----
|
|
605
|
+
|
|
606
|
+
*Output*:
|
|
607
|
+
[source,xml]
|
|
608
|
+
----
|
|
609
|
+
<item xmlns="https://example.com/ns" xmlns:ex="https://example.com/ns">
|
|
610
|
+
<ex:child>
|
|
611
|
+
<grandchild>
|
|
612
|
+
<value>x</value>
|
|
613
|
+
</grandchild>
|
|
614
|
+
</ex:child>
|
|
615
|
+
</item>
|
|
616
|
+
----
|
|
617
|
+
|
|
618
|
+
* `<ex:child>` is qualified because the *Parent* rule has `form: :qualified`.
|
|
619
|
+
* `<grandchild>` is unprefixed because the *Child* rule has no `form:` and the Child's namespace is `:unqualified`.
|
|
620
|
+
* The Parent's `form: :qualified` does **not** cascade.
|
|
621
|
+
====
|
|
622
|
+
|
|
623
|
+
To qualify every level, either set `form: :qualified` on each mapping rule that needs it, or set `element_form_default :qualified` on the namespace so inheritance handles it.
|
|
624
|
+
|
|
625
|
+
== Qualification Precedence
|
|
626
|
+
|
|
627
|
+
When serializing an element, the namespace is resolved by checking the following sources in priority order. The first match wins.
|
|
628
|
+
|
|
629
|
+
[cols="1,4", options="header"]
|
|
630
|
+
|===
|
|
631
|
+
|Priority |Source
|
|
632
|
+
|
|
633
|
+
|1 (highest)
|
|
634
|
+
|Type-level namespace: the attribute's value type (a `Type::Value` subclass) declares `xml_namespace`. Wins over everything else.
|
|
635
|
+
|
|
636
|
+
|2
|
|
637
|
+
|Rule-level namespace: the mapping rule has an explicit `namespace:` option. *However*, if the parent's `element_form_default` is `:unqualified` AND the rule's namespace matches the parent's, the namespace is overridden to blank — W3C unqualified semantics for local elements.
|
|
638
|
+
|
|
639
|
+
|3
|
|
640
|
+
|`form: :unqualified` on the rule: force the element into no namespace.
|
|
641
|
+
|
|
642
|
+
|4
|
|
643
|
+
|Parent's `element_form_default :qualified` inheritance: the child inherits the parent's namespace — but only if the child model itself declares a namespace (W3C: `elementFormDefault` applies to locally-declared elements only).
|
|
644
|
+
|
|
645
|
+
|5
|
|
646
|
+
|`form: :qualified` on the rule: inherit the parent's namespace.
|
|
647
|
+
|
|
648
|
+
|6 (lowest)
|
|
649
|
+
|No namespace.
|
|
650
|
+
|===
|
|
651
|
+
|
|
652
|
+
This ladder is implemented in `Lutaml::Xml::Transformation::ElementBuilder#determine_element_namespace`. The `ElementFormOptionRule` decision rule reads the propagated `form` value during the planning phase and forces prefix (`:qualified`) or default (`:unqualified`) format accordingly.
|
|
653
|
+
|
|
512
654
|
== Type Namespaces
|
|
513
655
|
|
|
514
656
|
Value types can define their own namespaces:
|
|
@@ -546,7 +546,13 @@ the current value is the same as the default value.
|
|
|
546
546
|
|
|
547
547
|
|
|
548
548
|
|
|
549
|
-
=== Attribute as raw string
|
|
549
|
+
=== Attribute as raw string (deprecated)
|
|
550
|
+
|
|
551
|
+
[WARNING]
|
|
552
|
+
====
|
|
553
|
+
`attribute :name, :string, raw: true` is deprecated.
|
|
554
|
+
Use `map_element "name", to: :name, raw: :content` instead.
|
|
555
|
+
====
|
|
550
556
|
|
|
551
557
|
An attribute can be set to read the value as raw string for XML, by using the `raw: true` option.
|
|
552
558
|
|
|
@@ -9,6 +9,8 @@ This guide explains how XML namespaces work, particularly the distinction betwee
|
|
|
9
9
|
- **Qualified**: Element/attribute needs a namespace declared in the XML
|
|
10
10
|
- **Unqualified**: Element/attribute does not need a namespace declared (uses parent's namespace)
|
|
11
11
|
|
|
12
|
+
> **Authoritative reference:** For the `element_form_default` / `form:` system, the full qualification precedence ladder, and `form:` behavior on nested `Serializable` children, see [docs/_guides/xml-namespace-qualification.adoc](../_guides/xml-namespace-qualification.adoc). This tutorial is a conceptual introduction.
|
|
13
|
+
|
|
12
14
|
## Default Namespace Inheritance
|
|
13
15
|
|
|
14
16
|
**Critical Rule:** Default namespace inheritance ONLY applies to elements, NOT attributes.
|
|
@@ -441,6 +441,8 @@ end
|
|
|
441
441
|
|
|
442
442
|
NOTE: The `<comment>` element uses `xmlns=""` to explicitly declare blank namespace (form: :unqualified override).
|
|
443
443
|
|
|
444
|
+
TIP: The `form:` option also works on attributes whose value type is a nested `Serializable` model, not just simple types like `:string`. It is a per-rule override and does not propagate transitively to grandchildren. For the full qualification precedence ladder and worked nested-model examples, see link:../_guides/xml-namespace-qualification/#form-override-on-serializable-children[XML Namespace Qualification and Prefix Control].
|
|
445
|
+
|
|
444
446
|
== Section 3: Type Namespaces
|
|
445
447
|
|
|
446
448
|
=== General
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
This guide provides comprehensive information about XML namespace management in Lutaml::Model, including the `namespace_scope` directive, W3C compliance, and best practices for multi-namespace documents.
|
|
8
8
|
|
|
9
|
+
NOTE: This guide focuses on namespace *declaration* and *scope*. For the `element_form_default` / `form:` qualification system — including the precedence ladder and `form:` behavior on nested `Serializable` children — see the companion guide: link:./_guides/xml-namespace-qualification[XML Namespace Qualification and Prefix Control].
|
|
10
|
+
|
|
9
11
|
== Understanding XML Namespaces
|
|
10
12
|
|
|
11
13
|
XML namespaces provide a method to avoid element name conflicts by qualifying names used in XML documents through a URI reference.
|
|
@@ -13,6 +13,12 @@ declared in the XML, "unqualified" means the ELEMENT/ATTRIBUTE does not need a
|
|
|
13
13
|
namespace declared in the XML (i.e. we look to its parent's namespace to find
|
|
14
14
|
its namespace).
|
|
15
15
|
|
|
16
|
+
> **Authoritative reference:** For the full qualification precedence ladder,
|
|
17
|
+
> `form:` behavior on nested `Serializable` children, per-rule vs transitive
|
|
18
|
+
> scope, and worked examples, see
|
|
19
|
+
> [docs/_guides/xml-namespace-qualification.adoc](./_guides/xml-namespace-qualification.adoc).
|
|
20
|
+
> This document is a conceptual overview; the guide is the canonical reference.
|
|
21
|
+
|
|
16
22
|
When there is no XML Schema present:
|
|
17
23
|
* element "acts like" unqualified (because of default namespace inheritance, does not need prefix)
|
|
18
24
|
* attribute "acts like" qualified (because of no default namespace inheritance, requires prefix)
|
data/lib/lutaml/jsonld.rb
CHANGED
|
@@ -5,9 +5,6 @@ require_relative "rdf"
|
|
|
5
5
|
|
|
6
6
|
module Lutaml
|
|
7
7
|
module JsonLd
|
|
8
|
-
autoload :Context, "#{__dir__}/jsonld/context"
|
|
9
|
-
autoload :TermDefinition, "#{__dir__}/jsonld/term_definition"
|
|
10
|
-
autoload :Transform, "#{__dir__}/jsonld/transform"
|
|
11
8
|
autoload :Adapter, "#{__dir__}/jsonld/adapter"
|
|
12
9
|
end
|
|
13
10
|
end
|
|
@@ -16,7 +13,7 @@ Lutaml::Model::FormatRegistry.register(
|
|
|
16
13
|
:jsonld,
|
|
17
14
|
mapping_class: Lutaml::Rdf::Mapping,
|
|
18
15
|
adapter_class: Lutaml::JsonLd::Adapter,
|
|
19
|
-
transformer: Lutaml::
|
|
16
|
+
transformer: Lutaml::Rdf::LinkedDataTransform,
|
|
20
17
|
key_value: false,
|
|
21
18
|
rdf: true,
|
|
22
19
|
error_types: ["JSON::ParserError"],
|
|
@@ -665,6 +665,10 @@ instance_object = nil)
|
|
|
665
665
|
def process_options!
|
|
666
666
|
validate_options!(@options)
|
|
667
667
|
@raw = !!@options[:raw]
|
|
668
|
+
if @raw
|
|
669
|
+
warn "[DEPRECATED] attribute :#{name}, :string, raw: true is deprecated. " \
|
|
670
|
+
"Use map_element \"name\", to: :#{name}, raw: :content instead."
|
|
671
|
+
end
|
|
668
672
|
@validations = @options[:validations]
|
|
669
673
|
set_default_for_collection if collection?
|
|
670
674
|
end
|
data/lib/lutaml/model/choice.rb
CHANGED
|
@@ -36,6 +36,36 @@ module Lutaml
|
|
|
36
36
|
@attributes << @model.attribute(name, type, options)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
# Restrict options on a predefined or imported attribute within this choice
|
|
40
|
+
#
|
|
41
|
+
# @param name [Symbol] The attribute name to restrict
|
|
42
|
+
# @param options [Hash] New options to merge
|
|
43
|
+
# @return [Symbol] The attribute name
|
|
44
|
+
def restrict(name, options = {})
|
|
45
|
+
@model.restrict(name, options)
|
|
46
|
+
attr = @model.attributes[name]
|
|
47
|
+
unless @attributes.include?(attr)
|
|
48
|
+
attr.options[:choice] = self
|
|
49
|
+
@attributes << attr
|
|
50
|
+
end
|
|
51
|
+
invalidate_cache!
|
|
52
|
+
name
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Remove an attribute from this choice block
|
|
56
|
+
#
|
|
57
|
+
# @param name [Symbol] The attribute name to remove
|
|
58
|
+
# @return [Boolean] true if the attribute was removed
|
|
59
|
+
def remove_attribute(name)
|
|
60
|
+
attr = @attributes.find { |a| !a.is_a?(Choice) && a.name == name }
|
|
61
|
+
return nil unless attr
|
|
62
|
+
|
|
63
|
+
@attributes.delete(attr)
|
|
64
|
+
attr.options.delete(:choice)
|
|
65
|
+
invalidate_cache!
|
|
66
|
+
attr
|
|
67
|
+
end
|
|
68
|
+
|
|
39
69
|
def choice(min: 1, max: 1, &block)
|
|
40
70
|
@attributes << Choice.new(@model, min, max, format: @format).tap do |c|
|
|
41
71
|
c.instance_eval(&block)
|
|
@@ -163,6 +193,10 @@ register = nil)
|
|
|
163
193
|
|
|
164
194
|
private
|
|
165
195
|
|
|
196
|
+
def invalidate_cache!
|
|
197
|
+
@flat_attributes = nil
|
|
198
|
+
end
|
|
199
|
+
|
|
166
200
|
def raise_errors(choices_hash)
|
|
167
201
|
flat_attr_names = flat_attributes.map { |attr| attr.name.to_s }
|
|
168
202
|
choices_hash.each do |choice_attr, count|
|
|
@@ -98,6 +98,13 @@ module Lutaml
|
|
|
98
98
|
!custom_methods.empty?
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
+
# Check if form is explicitly set
|
|
102
|
+
#
|
|
103
|
+
# @return [Boolean] true if form option was provided
|
|
104
|
+
def form_set?
|
|
105
|
+
!form.nil?
|
|
106
|
+
end
|
|
107
|
+
|
|
101
108
|
# Check if a name matches this rule (including alias names for multiple mappings)
|
|
102
109
|
#
|
|
103
110
|
# @param name [String, Symbol] The name to check
|
data/lib/lutaml/model/version.rb
CHANGED
data/lib/lutaml/model.rb
CHANGED
|
@@ -11,6 +11,7 @@ module Lutaml
|
|
|
11
11
|
autoload :Yamls, "#{__dir__}/yamls"
|
|
12
12
|
autoload :Xml, "#{__dir__}/xml"
|
|
13
13
|
autoload :JsonLd, "#{__dir__}/jsonld"
|
|
14
|
+
autoload :YamlLd, "#{__dir__}/yamlld"
|
|
14
15
|
autoload :Turtle, "#{__dir__}/turtle"
|
|
15
16
|
|
|
16
17
|
module Model
|
|
@@ -149,7 +150,8 @@ module Lutaml
|
|
|
149
150
|
"#{__dir__}/model/error/incorrect_sequence_error"
|
|
150
151
|
autoload :ChoiceUpperBoundError,
|
|
151
152
|
"#{__dir__}/model/error/choice_upper_bound_error"
|
|
152
|
-
autoload :TypeOnlyMappingError,
|
|
153
|
+
autoload :TypeOnlyMappingError,
|
|
154
|
+
"#{__dir__}/model/error/type_only_mapping_error"
|
|
153
155
|
autoload :NoRootMappingError, "#{__dir__}/model/error/no_root_mapping_error"
|
|
154
156
|
autoload :ImportModelWithRootError,
|
|
155
157
|
"#{__dir__}/model/error/import_model_with_root_error"
|