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
@@ -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
- The `map_all` tag in XML mapping captures and maps all content within an XML
1104
- element into a single attribute in the target Ruby object.
1101
+ [[xml-raw-element]]
1102
+ ==== Capturing raw XML content
1105
1103
 
1106
- The use case for `map_all` is to tell Lutaml::Model to not parse the content of
1107
- the XML element at all, and instead handle it as an XML string.
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
- NOTE: The corresponding method for key-value formats is at <<key-value-map-all>>.
1108
+ ===== `raw: :element` on `map_element` -- full element capture (recommended)
1110
1109
 
1111
- WARNING: Notice that usage of mapping all will lead to incompatibility between
1112
- serialization formats, i.e. the raw string content will not be portable as
1113
- objects are across different formats.
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
- This is useful in the case where the content of an XML element is not to be
1116
- handled by a Lutaml::Model::Serializable object.
1115
+ .Syntax
1116
+ [source,ruby]
1117
+ ----
1118
+ xml do
1119
+ map_element "svg", to: :svg_data, raw: :element
1120
+ end
1121
+ ----
1117
1122
 
1118
- This feature is commonly used with custom methods or a custom model object to
1119
- handle the content.
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
- This includes:
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
- * nested tags
1124
- * attributes
1125
- * text nodes
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
- The `map_all` tag is **exclusive** and cannot be combined with other mappings
1128
- (`map_element`, `map_content`) except for `map_attribute` for the same element,
1129
- ensuring it captures the entire inner XML content.
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
- NOTE: An error is raised if `map_all` is defined alongside any other mapping in
1132
- the same XML mapping context.
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
- Syntax:
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::JsonLd::Transform,
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
@@ -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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.15"
5
+ VERSION = "0.8.17"
6
6
  end
7
7
  end
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, "#{__dir__}/model/error/type_only_mapping_error"
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"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lutaml
4
- module JsonLd
4
+ module Rdf
5
5
  class Context
6
6
  attr_reader :prefixes, :terms
7
7