lutaml-model 0.8.16 → 0.8.18
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/downstream-performance.yml +27 -1
- data/.github/workflows/js-pr-check.yml +102 -0
- data/.github/workflows/js-sync.yml +51 -0
- data/.github/workflows/opal.yml +26 -6
- data/.github/workflows/performance.yml +32 -7
- data/.github/workflows/rake.yml +78 -32
- data/.github/workflows/release.yml +0 -3
- data/.gitmodules +6 -0
- data/.rubocop_todo.yml +76 -12
- data/Gemfile +16 -1
- data/README.adoc +267 -0
- data/Rakefile +166 -4
- data/docs/_guides/schema-import.adoc +42 -0
- data/docs/_guides/xml/namespace-semantics.adoc +2 -0
- data/docs/_guides/xml-mapping.adoc +21 -0
- data/docs/_guides/xml-namespace-qualification.adoc +142 -0
- data/docs/_pages/validation.adoc +65 -0
- 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/compat/opal/generate_boot.rb +123 -0
- data/lib/compat/opal/js_bundle_entry.rb +42 -0
- data/lib/compat/opal/lutaml_model_boot.rb +497 -0
- data/lib/compat/opal/moxml_boot.rb +68 -0
- data/lib/compat/opal/yaml_compat.rb +32 -0
- data/lib/lutaml/json.rb +1 -1
- data/lib/lutaml/jsonld.rb +1 -4
- data/lib/lutaml/key_value/transform.rb +3 -8
- data/lib/lutaml/key_value/transformation/value_serializer.rb +14 -1
- data/lib/lutaml/key_value/transformation.rb +17 -5
- data/lib/lutaml/model/adapter_resolver.rb +2 -2
- data/lib/lutaml/model/attribute.rb +88 -11
- data/lib/lutaml/model/cached_type_resolver.rb +1 -1
- data/lib/lutaml/model/choice.rb +34 -0
- data/lib/lutaml/model/compiled_rule.rb +7 -0
- data/lib/lutaml/model/error/union_schema_unsupported_error.rb +11 -0
- data/lib/lutaml/model/global_context.rb +11 -8
- data/lib/lutaml/model/instrumentation.rb +2 -2
- data/lib/lutaml/model/mapping/mapping_rule.rb +0 -6
- data/lib/lutaml/model/register.rb +2 -2
- data/lib/lutaml/model/runtime_compatibility.rb +50 -0
- data/lib/lutaml/model/schema/class_loader.rb +58 -0
- data/lib/lutaml/model/schema/compiled_output.rb +83 -0
- data/lib/lutaml/model/schema/definitions/attribute.rb +32 -0
- data/lib/lutaml/model/schema/definitions/choice.rb +20 -0
- data/lib/lutaml/model/schema/definitions/facet.rb +40 -0
- data/lib/lutaml/model/schema/definitions/group_import.rb +22 -0
- data/lib/lutaml/model/schema/definitions/member_walk.rb +33 -0
- data/lib/lutaml/model/schema/definitions/model.rb +40 -0
- data/lib/lutaml/model/schema/definitions/namespace.rb +23 -0
- data/lib/lutaml/model/schema/definitions/restricted_type.rb +30 -0
- data/lib/lutaml/model/schema/definitions/sequence.rb +19 -0
- data/lib/lutaml/model/schema/definitions/simple_content.rb +22 -0
- data/lib/lutaml/model/schema/definitions/transform_facet.rb +21 -0
- data/lib/lutaml/model/schema/definitions/type_ref.rb +20 -0
- data/lib/lutaml/model/schema/definitions/union_type.rb +28 -0
- data/lib/lutaml/model/schema/definitions/xml_root.rb +20 -0
- data/lib/lutaml/model/schema/definitions.rb +24 -0
- data/lib/lutaml/model/schema/file_writer.rb +70 -0
- data/lib/lutaml/model/schema/generator/definitions_collection.rb +16 -2
- data/lib/lutaml/model/schema/generator/property.rb +21 -4
- data/lib/lutaml/model/schema/module_nesting.rb +31 -0
- data/lib/lutaml/model/schema/namespace_naming.rb +46 -0
- data/lib/lutaml/model/schema/registry_generator.rb +115 -0
- data/lib/lutaml/model/schema/renderers/base.rb +40 -0
- data/lib/lutaml/model/schema/renderers/mappings.rb +72 -0
- data/lib/lutaml/model/schema/renderers/member_decls.rb +115 -0
- data/lib/lutaml/model/schema/renderers/model.rb +121 -0
- data/lib/lutaml/model/schema/renderers/namespace.rb +30 -0
- data/lib/lutaml/model/schema/renderers/registration.rb +64 -0
- data/lib/lutaml/model/schema/renderers/required_files_calculator.rb +93 -0
- data/lib/lutaml/model/schema/renderers/restricted_type.rb +88 -0
- data/lib/lutaml/model/schema/renderers/union.rb +83 -0
- data/lib/lutaml/model/schema/renderers.rb +19 -0
- data/lib/lutaml/model/schema/rnc_compiler/source_resolver.rb +47 -0
- data/lib/lutaml/model/schema/rnc_compiler.rb +62 -0
- data/lib/lutaml/model/schema/rng_compiler/define_classifier.rb +91 -0
- data/lib/lutaml/model/schema/rng_compiler/element_visitor.rb +392 -0
- data/lib/lutaml/model/schema/rng_compiler/member_collector.rb +30 -0
- data/lib/lutaml/model/schema/rng_compiler/rng_helpers.rb +152 -0
- data/lib/lutaml/model/schema/rng_compiler/value_type_resolver.rb +125 -0
- data/lib/lutaml/model/schema/rng_compiler.rb +220 -0
- data/lib/lutaml/model/schema/templates.rb +162 -0
- data/lib/lutaml/model/schema/xml_compiler/element_order.rb +41 -0
- data/lib/lutaml/model/schema/xml_compiler/registry_generator.rb +31 -107
- data/lib/lutaml/model/schema/xml_compiler/spec_builder/complex_types.rb +191 -0
- data/lib/lutaml/model/schema/xml_compiler/spec_builder/members.rb +216 -0
- data/lib/lutaml/model/schema/xml_compiler/spec_builder/simple_types.rb +153 -0
- data/lib/lutaml/model/schema/xml_compiler/spec_builder.rb +167 -0
- data/lib/lutaml/model/schema/xml_compiler/supported_data_types.rb +87 -0
- data/lib/lutaml/model/schema/xml_compiler.rb +38 -517
- data/lib/lutaml/model/schema.rb +19 -0
- data/lib/lutaml/model/serialize/attribute_definition.rb +14 -2
- data/lib/lutaml/model/serialize/builder.rb +15 -2
- data/lib/lutaml/model/serialize/value_mapping.rb +9 -57
- data/lib/lutaml/model/serialize.rb +6 -9
- data/lib/lutaml/model/services/rule_value_extractor.rb +2 -3
- data/lib/lutaml/model/services.rb +0 -2
- data/lib/lutaml/model/store.rb +2 -1
- data/lib/lutaml/model/toml.rb +1 -1
- data/lib/lutaml/model/transform.rb +17 -39
- data/lib/lutaml/model/transformation_registry.rb +1 -1
- data/lib/lutaml/model/type.rb +1 -0
- data/lib/lutaml/model/union.rb +320 -0
- data/lib/lutaml/model/utils.rb +6 -0
- data/lib/lutaml/model/validation.rb +1 -4
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model.rb +19 -0
- 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/toml.rb +1 -1
- data/lib/lutaml/xml/adapter.rb +1 -1
- data/lib/lutaml/xml/adapter_element.rb +2 -2
- data/lib/lutaml/xml/adapter_loader.rb +1 -1
- data/lib/lutaml/xml/builder/base.rb +1 -1
- data/lib/lutaml/xml/builder.rb +1 -1
- data/lib/lutaml/xml/error/schema_validation_error.rb +25 -0
- data/lib/lutaml/xml/model_transform.rb +5 -1
- data/lib/lutaml/xml/schema/relaxng_schema.rb +5 -0
- data/lib/lutaml/xml/schema/xsd_schema.rb +4 -0
- data/lib/lutaml/xml/serialization/format_conversion.rb +48 -0
- data/lib/lutaml/xml/serialization/instance_methods.rb +13 -0
- data/lib/lutaml/xml/transformation/element_builder.rb +59 -31
- data/lib/lutaml/xml/transformation/ordered_applier.rb +6 -0
- data/lib/lutaml/xml/xsd_validator.rb +66 -0
- data/lib/lutaml/xml.rb +53 -12
- data/lib/lutaml/yamlld/adapter.rb +25 -0
- data/lib/lutaml/yamlld.rb +25 -0
- data/lutaml-model.gemspec +4 -2
- data/spec/fixtures/xml/schema/rnc/address_book.rnc +15 -0
- data/spec/fixtures/xml/schema/rnc/book_features.rnc +18 -0
- data/spec/fixtures/xml/schema/rnc/includes/book.rnc +6 -0
- data/spec/fixtures/xml/schema/rnc/includes/circular_a.rnc +8 -0
- data/spec/fixtures/xml/schema/rnc/includes/circular_b.rnc +8 -0
- data/spec/fixtures/xml/schema/rnc/includes/library.rnc +8 -0
- data/spec/fixtures/xml/schema/rng/address_book.rng +16 -0
- data/spec/fixtures/xml/schema/rng/book.rng +27 -0
- data/spec/fixtures/xml/schema/rng/fixed_value.rng +11 -0
- data/spec/fixtures/xml/schema/rng/fragment_in_choice.rng +30 -0
- data/spec/fixtures/xml/schema/rng/full_name.rng +39 -0
- data/spec/fixtures/xml/schema/rng/integer_range.rng +32 -0
- data/spec/fixtures/xml/schema/rng/list_type.rng +12 -0
- data/spec/fixtures/xml/schema/rng/namespaced.rng +9 -0
- data/spec/fixtures/xml/schema/rng/paragraph.rng +13 -0
- data/spec/fixtures/xml/schema/rng/person.rng +23 -0
- data/spec/fixtures/xml/schema/rng/union.rng +14 -0
- data/spec/fixtures/xml/validate_xml_with_address.xsd +13 -0
- data/spec/fixtures/xml/validate_xml_with_contact.xsd +11 -0
- data/spec/fixtures/xml/validate_xml_with_person.xsd +11 -0
- data/spec/fixtures/xml/validate_xml_with_person_strict.xsd +17 -0
- data/spec/fixtures/xml/validate_xml_with_product.xsd +37 -0
- data/spec/lutaml/integration/multi_format_spec.rb +23 -0
- data/spec/lutaml/model/attribute_apply_value_map_spec.rb +134 -0
- data/spec/lutaml/model/attribute_spec.rb +177 -13
- data/spec/lutaml/model/boot_manifest_spec.rb +30 -0
- data/spec/lutaml/model/choice_restrict_spec.rb +225 -0
- data/spec/lutaml/model/collection_spec.rb +18 -0
- data/spec/lutaml/model/custom_model_spec.rb +4 -4
- data/spec/lutaml/model/mixed_content_spec.rb +173 -2
- data/spec/lutaml/model/multiple_mapping_spec.rb +4 -4
- data/spec/lutaml/model/opal_smoke_spec.rb +67 -2
- data/spec/lutaml/model/ordered_content_spec.rb +33 -3
- data/spec/lutaml/model/raw_element_spec.rb +125 -0
- data/spec/lutaml/model/rule_value_extractor_spec.rb +7 -14
- data/spec/lutaml/model/runtime_compatibility_spec.rb +25 -0
- data/spec/lutaml/model/schema/renderers/model_spec.rb +149 -0
- data/spec/lutaml/model/schema/renderers/namespace_spec.rb +36 -0
- data/spec/lutaml/model/schema/renderers/restricted_type_spec.rb +75 -0
- data/spec/lutaml/model/schema/renderers/union_spec.rb +58 -0
- data/spec/lutaml/model/transform_apply_value_map_spec.rb +64 -0
- data/spec/lutaml/model/uninitialized_class_spec.rb +1 -1
- data/spec/lutaml/model/union_attribute_spec.rb +658 -0
- data/spec/lutaml/model/union_spec.rb +257 -0
- data/spec/lutaml/model/utils_spec.rb +14 -0
- 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} +15 -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/opal_xml_spec.rb +22 -20
- data/spec/lutaml/xml/prefix_control_spec.rb +4 -4
- data/spec/lutaml/xml/schema/compiler_spec.rb +21 -4
- data/spec/lutaml/xml/schema/rnc_compiler_spec.rb +404 -0
- data/spec/lutaml/xml/schema/rng_compiler_spec.rb +893 -0
- 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/xml/validate_xml_with_constructs_spec.rb +222 -0
- data/spec/lutaml/xml/validate_xml_with_spec.rb +186 -0
- data/spec/lutaml/yamlld/adapter_spec.rb +56 -0
- data/spec/lutaml/yamlld/registration_spec.rb +33 -0
- data/spec/spec_helper.rb +28 -11
- data/spec/support/opal.rb +5 -2
- metadata +108 -37
- data/lib/lutaml/model/schema/xml_compiler/attribute.rb +0 -106
- data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +0 -47
- data/lib/lutaml/model/schema/xml_compiler/choice.rb +0 -67
- data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +0 -27
- data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +0 -34
- data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +0 -173
- data/lib/lutaml/model/schema/xml_compiler/element.rb +0 -121
- data/lib/lutaml/model/schema/xml_compiler/group.rb +0 -113
- data/lib/lutaml/model/schema/xml_compiler/restriction.rb +0 -104
- data/lib/lutaml/model/schema/xml_compiler/sequence.rb +0 -52
- data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +0 -40
- data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +0 -265
- data/lib/lutaml/model/schema/xml_compiler/xml_namespace_class.rb +0 -110
- data/lib/lutaml/model/services/default_value_resolver.rb +0 -60
- data/spec/lutaml/model/services/default_value_resolver_spec.rb +0 -162
- data/spec/lutaml/xml/schema/compiler/attribute_group_spec.rb +0 -71
- data/spec/lutaml/xml/schema/compiler/attribute_spec.rb +0 -67
- data/spec/lutaml/xml/schema/compiler/choice_spec.rb +0 -74
- data/spec/lutaml/xml/schema/compiler/complex_content_restriction_spec.rb +0 -60
- data/spec/lutaml/xml/schema/compiler/complex_content_spec.rb +0 -41
- data/spec/lutaml/xml/schema/compiler/complex_type_spec.rb +0 -202
- data/spec/lutaml/xml/schema/compiler/element_spec.rb +0 -68
- data/spec/lutaml/xml/schema/compiler/group_spec.rb +0 -90
- data/spec/lutaml/xml/schema/compiler/restriction_spec.rb +0 -77
- data/spec/lutaml/xml/schema/compiler/sequence_spec.rb +0 -65
- data/spec/lutaml/xml/schema/compiler/simple_content_spec.rb +0 -61
- data/spec/lutaml/xml/schema/compiler/simple_type_spec.rb +0 -184
|
@@ -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:
|
data/docs/_pages/validation.adoc
CHANGED
|
@@ -72,6 +72,71 @@ The `required: true` option validates that an attribute is present.
|
|
|
72
72
|
attribute :name, :string, required: true
|
|
73
73
|
----
|
|
74
74
|
|
|
75
|
+
=== XML schema validation
|
|
76
|
+
|
|
77
|
+
The `validate_xml_with` class macro validates the model's generated XML
|
|
78
|
+
against one or more XML Schema (XSD) files during `validate` / `validate!`.
|
|
79
|
+
|
|
80
|
+
[source,ruby]
|
|
81
|
+
----
|
|
82
|
+
class Person < Lutaml::Model::Serializable
|
|
83
|
+
attribute :name, :string
|
|
84
|
+
|
|
85
|
+
xml do
|
|
86
|
+
root "person"
|
|
87
|
+
map_element "name", to: :name
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
validate_xml_with "schemas/person.xsd"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
person = Person.new(name: "Alice")
|
|
94
|
+
person.validate # => collects Lutaml::Xml::Error::SchemaValidationError
|
|
95
|
+
# instances when the generated XML does not conform
|
|
96
|
+
person.validate! # => raises Lutaml::Model::ValidationError on nonconformance
|
|
97
|
+
----
|
|
98
|
+
|
|
99
|
+
The macro accepts one or more schema paths, and repeated calls append to the
|
|
100
|
+
configured list. Subclasses inherit the parent's schema paths parent-first;
|
|
101
|
+
paths added by a subclass do not affect the parent.
|
|
102
|
+
|
|
103
|
+
[source,ruby]
|
|
104
|
+
----
|
|
105
|
+
validate_xml_with "schemas/base.xsd", "schemas/extensions.xsd"
|
|
106
|
+
----
|
|
107
|
+
|
|
108
|
+
Raw XML strings can be checked against the same schemas with the explicit
|
|
109
|
+
class-level helpers, for example to reject nonconforming input before
|
|
110
|
+
`from_xml`:
|
|
111
|
+
|
|
112
|
+
[source,ruby]
|
|
113
|
+
----
|
|
114
|
+
Person.validate_xml(xml_string) # => array of schema validation errors
|
|
115
|
+
Person.validate_xml!(xml_string) # => raises Lutaml::Model::ValidationError
|
|
116
|
+
----
|
|
117
|
+
|
|
118
|
+
NOTE: `to_xml` and `from_xml` never validate implicitly. Call `validate!`
|
|
119
|
+
before serializing, or `validate_xml!` on incoming XML, when enforcement is
|
|
120
|
+
needed — the same explicit contract as every other validation type.
|
|
121
|
+
|
|
122
|
+
XSD validation uses Nokogiri, which is required lazily on first validation.
|
|
123
|
+
The schemas must be local files declared explicitly; `xsi:schemaLocation`
|
|
124
|
+
in documents is not read and remote schemas are not fetched. Relative paths
|
|
125
|
+
resolve against the file that declares `validate_xml_with`, so a model works
|
|
126
|
+
regardless of the process working directory.
|
|
127
|
+
|
|
128
|
+
Misconfiguration raises instead of being collected as validation errors:
|
|
129
|
+
|
|
130
|
+
* a missing schema file raises `Errno::ENOENT`;
|
|
131
|
+
* a malformed XSD raises `Nokogiri::XML::SyntaxError`;
|
|
132
|
+
* a malformed XML string passed to `validate_xml` / `validate_xml!` raises
|
|
133
|
+
`Nokogiri::XML::SyntaxError` — parsing is strict, so malformed input is
|
|
134
|
+
never silently repaired and reported as conforming;
|
|
135
|
+
* a missing nokogiri gem raises `Lutaml::Xml::Error::XmlConfigurationError`;
|
|
136
|
+
* a class using the macro without an XML root mapping raises
|
|
137
|
+
`Lutaml::Model::TypeOnlyMappingError` from `validate`, since validation
|
|
138
|
+
must serialize the model to check it.
|
|
139
|
+
|
|
75
140
|
|
|
76
141
|
== Validation examples
|
|
77
142
|
|
|
@@ -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)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string: true
|
|
2
|
+
|
|
3
|
+
# One-shot generator for lib/compat/opal/lutaml_model_boot.rb.
|
|
4
|
+
#
|
|
5
|
+
# Walks lib/lutaml/**/*.rb and extracts every `autoload :Name, "path"`
|
|
6
|
+
# declaration (single-line or multi-line), resolving the standard
|
|
7
|
+
# `"#{__dir__}/..."` and `"#{File.dirname(__FILE__)}/..."` interpolation
|
|
8
|
+
# patterns relative to the declaring file's directory.
|
|
9
|
+
#
|
|
10
|
+
# Opal treats autoload as a no-op, so every autoload target must appear
|
|
11
|
+
# here as an explicit `require` for nested constants to resolve at runtime.
|
|
12
|
+
#
|
|
13
|
+
# Run after adding/removing any autoload in lib/lutaml/:
|
|
14
|
+
# ruby lib/compat/opal/generate_boot.rb
|
|
15
|
+
|
|
16
|
+
require "pathname"
|
|
17
|
+
|
|
18
|
+
REPO_ROOT = Pathname.new(File.expand_path("../../..", __dir__))
|
|
19
|
+
LIB = REPO_ROOT.join("lib")
|
|
20
|
+
|
|
21
|
+
# Patterns excluded under Opal because they pull in native-only deps
|
|
22
|
+
# (Nokogiri/Ox C extensions, Thor CLI, etc.). REXML is pure Ruby and
|
|
23
|
+
# ships in the bundled gem + moxml's lib/compat/opal/rexml/* shadows,
|
|
24
|
+
# so it is NOT excluded — it is a fully working second Opal adapter.
|
|
25
|
+
NATIVE_ONLY_PATTERNS = [
|
|
26
|
+
%r{(nokogiri|ox)_adapter\z},
|
|
27
|
+
%r{/schema/(relaxng|xsd)\b},
|
|
28
|
+
%r{/schema_builder/(nokogiri|oga)\b},
|
|
29
|
+
%r{/schema/builder/(nokogiri|oga)\b},
|
|
30
|
+
%r{\Alutaml/model/cli\z},
|
|
31
|
+
].freeze
|
|
32
|
+
|
|
33
|
+
# Match `autoload :Name, "...anything..."` where the string argument may
|
|
34
|
+
# span multiple physical lines (whitespace after the comma).
|
|
35
|
+
DECL_RE = /autoload\s+:[A-Za-z0-9_]+\s*,\s*"([^"]+)"/
|
|
36
|
+
|
|
37
|
+
# Stitches multi-line autoload declarations into a single logical line so
|
|
38
|
+
# DECL_RE can match. We only merge when the line ends right after the
|
|
39
|
+
# comma that follows `autoload :Name`.
|
|
40
|
+
def join_multiline_autoloads(src)
|
|
41
|
+
src.gsub(/(autoload\s+:[A-Za-z0-9_]+\s*,)\s*\n\s*/) { "#{Regexp.last_match(1)} " }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Resolve an autoload's raw string argument to a require path relative to
|
|
45
|
+
# LIB. Both common interpolation shapes resolve relative to the declaring
|
|
46
|
+
# file's directory, which is what MRI does for them.
|
|
47
|
+
def resolve_path(raw, declaring_file)
|
|
48
|
+
declaring_dir = Pathname.new(declaring_file).dirname
|
|
49
|
+
|
|
50
|
+
if raw.start_with?("\#{__dir__}/")
|
|
51
|
+
subdir = raw.sub("\#{__dir__}/", "").delete_suffix(".rb")
|
|
52
|
+
(declaring_dir + subdir).relative_path_from(LIB).to_s.delete_suffix(".rb")
|
|
53
|
+
elsif raw.start_with?("\#{File.dirname(__FILE__)}/")
|
|
54
|
+
subdir = raw.sub("\#{File.dirname(__FILE__)}/", "").delete_suffix(".rb")
|
|
55
|
+
(declaring_dir + subdir).relative_path_from(LIB).to_s.delete_suffix(".rb")
|
|
56
|
+
elsif raw.start_with?("lutaml/")
|
|
57
|
+
raw.delete_suffix(".rb")
|
|
58
|
+
else
|
|
59
|
+
raw.delete_suffix(".rb")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
paths = []
|
|
64
|
+
Dir.glob("#{LIB}/lutaml/**/*.rb").each do |file|
|
|
65
|
+
src = File.read(file)
|
|
66
|
+
# Strip full-line and trailing comments so commented-out autoload
|
|
67
|
+
# examples (e.g. in type_registry.rb) don't end up in the boot file.
|
|
68
|
+
src = src.lines.reject { |line| line.lstrip.start_with?("#") }.join
|
|
69
|
+
join_multiline_autoloads(src).scan(DECL_RE).each do |(raw)|
|
|
70
|
+
paths << resolve_path(raw, file)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
paths = paths.uniq.sort
|
|
75
|
+
native_only = paths.select { |p| NATIVE_ONLY_PATTERNS.any? { |re| p =~ re } }
|
|
76
|
+
opal_paths = paths - native_only
|
|
77
|
+
|
|
78
|
+
# Order matters: namespace-defining entry files must load before any of
|
|
79
|
+
# their nested files. lutaml/model.rb declares the Lutaml::Model module
|
|
80
|
+
# (referenced by every other file); lutaml/xml.rb declares Lutaml::Xml.
|
|
81
|
+
# After the entry files, load by depth-then-name so a parent file (e.g.
|
|
82
|
+
# lutaml/hash_format.rb) is required before its children (e.g.
|
|
83
|
+
# lutaml/hash_format/adapter/document.rb).
|
|
84
|
+
#
|
|
85
|
+
# ENTRY_FILES are added unconditionally: they are top-level entry points
|
|
86
|
+
# that nothing else autoloads (because they ARE the entry points users
|
|
87
|
+
# require directly), so they would otherwise be missing from the list.
|
|
88
|
+
ENTRY_FILES = %w[lutaml/model lutaml/xml].freeze
|
|
89
|
+
remaining = (opal_paths - ENTRY_FILES).sort_by { |p| [p.count("/"), p] }
|
|
90
|
+
ordered = ENTRY_FILES + remaining
|
|
91
|
+
|
|
92
|
+
out = []
|
|
93
|
+
out << "# frozen_string_literal: true"
|
|
94
|
+
out << ""
|
|
95
|
+
out << "# DO NOT EDIT — regenerate with:"
|
|
96
|
+
out << "# ruby lib/compat/opal/generate_boot.rb"
|
|
97
|
+
out << "#"
|
|
98
|
+
out << "# Opal does not support Ruby autoload (it is a no-op at parse time)."
|
|
99
|
+
out << "# This file explicitly requires every autoload target in lib/lutaml/**"
|
|
100
|
+
out << "# so that nested constants resolve at runtime under Opal."
|
|
101
|
+
out << "#"
|
|
102
|
+
out << "# Native-only paths excluded under Opal:"
|
|
103
|
+
out << "# - nokogiri/ox/rexml adapters (C extensions unavailable)"
|
|
104
|
+
out << "# - XSD / RELAX NG schema generators (require Nokogiri)"
|
|
105
|
+
out << "# - lutaml/model/cli (thor-based, runtime-irrelevant under Opal)"
|
|
106
|
+
out << "#"
|
|
107
|
+
out << "# Ordering: lutaml/model and lutaml/xml define the Lutaml::Model and"
|
|
108
|
+
out << "# Lutaml::Xml namespaces, so they load first. Remaining paths are"
|
|
109
|
+
out << "# ordered by depth (shallower first) so parent files declare their"
|
|
110
|
+
out << "# child namespaces before children load."
|
|
111
|
+
out << "#"
|
|
112
|
+
out << "# Loaded by the Opal Rakefile before spec_helper."
|
|
113
|
+
out << ""
|
|
114
|
+
ordered.each { |p| out << %(require "#{p}") }
|
|
115
|
+
out << ""
|
|
116
|
+
|
|
117
|
+
# OPAL_BOOT_OUTPUT lets the in-sync guard spec regenerate to a temp path
|
|
118
|
+
# without clobbering the committed manifest; unset, it writes canonically.
|
|
119
|
+
target = ENV.fetch("OPAL_BOOT_OUTPUT") do
|
|
120
|
+
File.expand_path("lutaml_model_boot.rb", __dir__)
|
|
121
|
+
end
|
|
122
|
+
File.write(target, out.join("\n"))
|
|
123
|
+
warn "Generated #{ordered.size} requires (excluded #{native_only.size} native-only paths) -> #{target}"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# JS bundle entry point. Pulls in the Opal-only compat shims and boot
|
|
4
|
+
# files BEFORE lutaml/model so that moxml's adapter constants, the
|
|
5
|
+
# String/Encoding/YAML/Array#pack patches, and the eager-loaded
|
|
6
|
+
# Moxml::* / Lutaml::* constants are all in place by the time
|
|
7
|
+
# lib/lutaml/model.rb runs.
|
|
8
|
+
#
|
|
9
|
+
# Used by scripts/build.rb as the bundle ENTRY (instead of
|
|
10
|
+
# "lutaml/model" directly) so the JS bundle mirrors the load order
|
|
11
|
+
# that lutaml-model's Rakefile applies for `rake spec:opal`.
|
|
12
|
+
|
|
13
|
+
if RUBY_ENGINE == "opal"
|
|
14
|
+
# 0. runtime_compatibility first: it stubs Mutex/ConditionVariable/
|
|
15
|
+
# Thread/WeakRef and patches Module#prepend for Opal. Oga's LRU
|
|
16
|
+
# and other gems reference Mutex at file-load time, so this must
|
|
17
|
+
# run before any gem that touches thread-safety primitives.
|
|
18
|
+
require "lutaml/model/runtime_compatibility"
|
|
19
|
+
|
|
20
|
+
# 0a. Opal's stdlib StringIO < IO loads here so moxml's
|
|
21
|
+
# `class StringIO` (bare, no parent) in rexml_compat.rb re-opens
|
|
22
|
+
# the existing class instead of conflicting with `< ::IO`.
|
|
23
|
+
# Without this, the bare-class definition runs first and Opal's
|
|
24
|
+
# `< IO` definition later raises "superclass mismatch".
|
|
25
|
+
require "stringio"
|
|
26
|
+
|
|
27
|
+
# 1. stdlib shims: String/Encoding/StringIO + Array#pack + nodejs/yaml
|
|
28
|
+
require "rexml_compat"
|
|
29
|
+
require "yaml_compat"
|
|
30
|
+
|
|
31
|
+
# 2. forks' Opal-aware conditionals select the pure-Ruby lexer
|
|
32
|
+
require "oga"
|
|
33
|
+
require "ll/setup"
|
|
34
|
+
|
|
35
|
+
# 3. eager-load boots (Opal ignores autoload)
|
|
36
|
+
require "moxml_boot"
|
|
37
|
+
require "lutaml_model_boot"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# 4. the actual entry point
|
|
41
|
+
require "lutaml/model"
|
|
42
|
+
require "lutaml/xml"
|