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
|
@@ -91,18 +91,21 @@ RSpec.describe "XML Namespace Placement" do
|
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
# This is the INCORRECT pattern - namespace at class level for Models
|
|
94
|
-
broken_model =
|
|
95
|
-
|
|
94
|
+
broken_model = nil
|
|
95
|
+
expect do
|
|
96
|
+
broken_model = Class.new do
|
|
97
|
+
include Lutaml::Model::Serialize
|
|
96
98
|
|
|
97
|
-
|
|
99
|
+
namespace ns # ❌ This doesn't work for Serializable classes!
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
attribute :value, :string
|
|
100
102
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
xml do
|
|
104
|
+
element "broken"
|
|
105
|
+
map_content to: :value
|
|
106
|
+
end
|
|
104
107
|
end
|
|
105
|
-
end
|
|
108
|
+
end.to output(/DEPRECATION WARNING.*Class-level.*namespace/).to_stderr
|
|
106
109
|
|
|
107
110
|
parent_ns = Class.new(Lutaml::Xml::W3c::XmlNamespace) do
|
|
108
111
|
uri "http://example.com/parent"
|
|
@@ -251,7 +251,7 @@ RSpec.describe "Three-phase namespace algorithm" do
|
|
|
251
251
|
expect(xml).to include("<version>4.0</version>")
|
|
252
252
|
|
|
253
253
|
# Should NOT add redundant prefix
|
|
254
|
-
expect(xml).not_to
|
|
254
|
+
expect(xml).not_to include("<vcard:version>")
|
|
255
255
|
end
|
|
256
256
|
end
|
|
257
257
|
end
|
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
require "spec_helper"
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# Adapter-agnostic XML round-trip scenarios, shared between Oga and
|
|
6
|
+
# REXML smoke runs. Both adapters are pure Ruby and work under Opal;
|
|
7
|
+
# this shared example is the contract that any future Opal-capable
|
|
8
|
+
# adapter would also need to satisfy.
|
|
9
|
+
RSpec.shared_examples "opal XML adapter round-trip" do |adapter_name|
|
|
6
10
|
before do
|
|
7
|
-
Lutaml::Model::Config.xml_adapter_type =
|
|
11
|
+
Lutaml::Model::Config.xml_adapter_type = adapter_name
|
|
8
12
|
end
|
|
9
13
|
|
|
10
14
|
it "round-trips XML through parse and serialize" do
|
|
@@ -118,28 +122,26 @@ RSpec.describe "XML with REXML under Opal", if: RUBY_ENGINE == "opal" do
|
|
|
118
122
|
expect(xml).to include("<title>Widget</title>")
|
|
119
123
|
expect(xml).to include("<count>5</count>")
|
|
120
124
|
end
|
|
125
|
+
end
|
|
121
126
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
127
|
+
# Pure-Ruby XML adapters that ship in the Opal bundle. Each nested
|
|
128
|
+
# group runs the shared contract for its adapter; the top-level guard
|
|
129
|
+
# gates the whole file under Opal.
|
|
130
|
+
RSpec.describe "XML adapters under Opal", if: RUBY_ENGINE == "opal" do
|
|
131
|
+
describe "with Oga (default)" do
|
|
132
|
+
it_behaves_like "opal XML adapter round-trip", :oga
|
|
126
133
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const_set(:MODEL_CLASS, plain_model)
|
|
131
|
-
model plain_model
|
|
132
|
-
attribute :data, :string
|
|
133
|
-
xml do
|
|
134
|
-
element "data"
|
|
135
|
-
map_element "data", to: :data
|
|
136
|
-
end
|
|
134
|
+
it "uses Oga adapter by default under Opal" do
|
|
135
|
+
expect(Lutaml::Model::Config.xml_adapter_type).to eq(:oga)
|
|
137
136
|
end
|
|
138
|
-
|
|
139
|
-
expect(plain_model.ancestors).to include(Lutaml::Xml::XmlOrderable)
|
|
140
137
|
end
|
|
141
138
|
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
describe "with REXML" do
|
|
140
|
+
it_behaves_like "opal XML adapter round-trip", :rexml
|
|
141
|
+
|
|
142
|
+
it "selects REXML when explicitly configured" do
|
|
143
|
+
Lutaml::Model::Config.xml_adapter_type = :rexml
|
|
144
|
+
expect(Lutaml::Model::Config.xml_adapter_type).to eq(:rexml)
|
|
145
|
+
end
|
|
144
146
|
end
|
|
145
147
|
end
|
|
@@ -100,8 +100,8 @@ RSpec.describe "XML Prefix Control" do
|
|
|
100
100
|
expect(xml).to include('<custom:Properties xmlns:custom="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">')
|
|
101
101
|
expect(xml).to include("<custom:Template>Normal.dotm</custom:Template>")
|
|
102
102
|
# Should not have redundant declarations
|
|
103
|
-
expect(xml).not_to
|
|
104
|
-
expect(xml).not_to
|
|
103
|
+
expect(xml).not_to include('xmlns="')
|
|
104
|
+
expect(xml).not_to include("xmlns:app=")
|
|
105
105
|
end
|
|
106
106
|
end
|
|
107
107
|
|
|
@@ -208,8 +208,8 @@ RSpec.describe "XML Prefix Control" do
|
|
|
208
208
|
expect(xml).to include('<custom:Properties xmlns:custom="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">')
|
|
209
209
|
expect(xml).to include("<custom:Template>Normal.dotm</custom:Template>")
|
|
210
210
|
# Should not have redundant declarations
|
|
211
|
-
expect(xml).not_to
|
|
212
|
-
expect(xml).not_to
|
|
211
|
+
expect(xml).not_to include('xmlns="')
|
|
212
|
+
expect(xml).not_to include("xmlns:app=")
|
|
213
213
|
end
|
|
214
214
|
end
|
|
215
215
|
|
|
@@ -41,8 +41,9 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
|
|
|
41
41
|
it "keeps built-in W3C XML attribute types when processing xml.xsd" do
|
|
42
42
|
described_class.as_models(xml_namespace_schema)
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
type_ref = described_class.builder.attributes["id"].type
|
|
45
|
+
expect(type_ref.kind).to eq(:w3c)
|
|
46
|
+
expect(type_ref.value).to eq("Lutaml::Xml::W3c::XmlIdType")
|
|
46
47
|
end
|
|
47
48
|
end
|
|
48
49
|
|
|
@@ -227,8 +228,7 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
|
|
|
227
228
|
end
|
|
228
229
|
|
|
229
230
|
let(:expected_classes) do
|
|
230
|
-
|
|
231
|
-
classes = types.filter_map do |name, value|
|
|
231
|
+
classes = described_class::SupportedDataTypes::TABLE.filter_map do |name, value|
|
|
232
232
|
name.to_s unless value[:skippable]
|
|
233
233
|
end
|
|
234
234
|
classes << "User"
|
|
@@ -751,4 +751,21 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
|
|
|
751
751
|
end
|
|
752
752
|
end
|
|
753
753
|
end
|
|
754
|
+
|
|
755
|
+
describe "SupportedDataTypes.skippable?" do
|
|
756
|
+
# Attribute TypeRefs store the snake_case rendering, simple_content
|
|
757
|
+
# keeps the XSD spelling; skippable? must accept both for dateTime,
|
|
758
|
+
# the one skippable name that changes under snake_case.
|
|
759
|
+
it "matches both the XSD and snake_case spelling of skippable types" do
|
|
760
|
+
sdt = described_class::SupportedDataTypes
|
|
761
|
+
expect(sdt.skippable?("dateTime")).to be true
|
|
762
|
+
expect(sdt.skippable?("date_time")).to be true
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
it "still rejects non-skippable types in both spellings" do
|
|
766
|
+
sdt = described_class::SupportedDataTypes
|
|
767
|
+
expect(sdt.skippable?("nonNegativeInteger")).to be false
|
|
768
|
+
expect(sdt.skippable?("non_negative_integer")).to be false
|
|
769
|
+
end
|
|
770
|
+
end
|
|
754
771
|
end
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
require "rng"
|
|
2
|
+
require "spec_helper"
|
|
3
|
+
require "lutaml/model/schema"
|
|
4
|
+
require "tmpdir"
|
|
5
|
+
|
|
6
|
+
RSpec.describe Lutaml::Model::Schema::RncCompiler do
|
|
7
|
+
before do
|
|
8
|
+
default_ctx = Lutaml::Model::GlobalContext.context(:default)
|
|
9
|
+
if default_ctx
|
|
10
|
+
default_ctx.registry.clear
|
|
11
|
+
Lutaml::Model::Type.register_builtin_types_in(default_ctx.registry)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe ".to_models" do
|
|
16
|
+
context "with an RNC schema using named patterns and start |=" do
|
|
17
|
+
let(:schema) { File.read("spec/fixtures/xml/schema/rnc/address_book.rnc") }
|
|
18
|
+
let(:sources) { described_class.to_models(schema) }
|
|
19
|
+
|
|
20
|
+
it "compiles RNC through the RNG compiler" do
|
|
21
|
+
expect(sources.keys).to contain_exactly(
|
|
22
|
+
"AddressBook", "Card", "Name", "Email"
|
|
23
|
+
)
|
|
24
|
+
expect(sources["AddressBook"]).to include("class AddressBook")
|
|
25
|
+
expect(sources["AddressBook"]).to include('map_element "card", to: :card')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "does not emit a synthetic Start class for the RNC start pattern" do
|
|
29
|
+
expect(sources).not_to have_key("Start")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context "with an inline start containing a named reference" do
|
|
34
|
+
let(:schema) do
|
|
35
|
+
<<~RNC
|
|
36
|
+
item = element item { text }
|
|
37
|
+
start = element root { item* }
|
|
38
|
+
RNC
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "generates the root model, not just the referenced child" do
|
|
42
|
+
sources = described_class.to_models(schema)
|
|
43
|
+
|
|
44
|
+
expect(sources.keys).to contain_exactly("Root", "Item")
|
|
45
|
+
expect(sources["Root"]).to include("class Root")
|
|
46
|
+
expect(sources["Root"]).to include('map_element "item", to: :item')
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
context "with a ref-only inline start defined after its target" do
|
|
51
|
+
let(:schema) do
|
|
52
|
+
<<~RNC
|
|
53
|
+
library = element library { text }
|
|
54
|
+
start = library
|
|
55
|
+
RNC
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "roots the referenced define without emitting a Start class" do
|
|
59
|
+
sources = described_class.to_models(schema)
|
|
60
|
+
|
|
61
|
+
expect(sources.keys).to include("Library")
|
|
62
|
+
expect(sources).not_to have_key("Start")
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
context "with create_files output" do
|
|
67
|
+
let(:dir) { Dir.mktmpdir }
|
|
68
|
+
let(:schema) { File.read("spec/fixtures/xml/schema/rnc/address_book.rnc") }
|
|
69
|
+
|
|
70
|
+
before do
|
|
71
|
+
stub_const("RncAddressBookSpec", Module.new)
|
|
72
|
+
described_class.to_models(
|
|
73
|
+
schema,
|
|
74
|
+
output_dir: dir,
|
|
75
|
+
create_files: true,
|
|
76
|
+
module_namespace: "RncAddressBookSpec",
|
|
77
|
+
)
|
|
78
|
+
require File.join(dir, "rncaddressbookspec_registry.rb")
|
|
79
|
+
RncAddressBookSpec.register_all
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
after do
|
|
83
|
+
FileUtils.rm_rf(dir)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "writes namespaced files and registry output" do
|
|
87
|
+
module_dir = File.join(dir, "rncaddressbookspec")
|
|
88
|
+
expect(File).to exist(File.join(dir, "rncaddressbookspec_registry.rb"))
|
|
89
|
+
expect(File).to exist(File.join(module_dir, "address_book.rb"))
|
|
90
|
+
expect(File).to exist(File.join(module_dir, "card.rb"))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "loads generated classes that can parse XML" do
|
|
94
|
+
xml = <<~XML
|
|
95
|
+
<addressBook>
|
|
96
|
+
<card>
|
|
97
|
+
<name>Alice</name>
|
|
98
|
+
<email>alice@example.com</email>
|
|
99
|
+
</card>
|
|
100
|
+
</addressBook>
|
|
101
|
+
XML
|
|
102
|
+
|
|
103
|
+
book = RncAddressBookSpec::AddressBook.from_xml(xml)
|
|
104
|
+
expect(book.card.first.name.content).to eq("Alice")
|
|
105
|
+
expect(book.card.first.email.content).to eq("alice@example.com")
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
context "with RNC-specific syntax used by RFC XML schemas" do
|
|
110
|
+
let(:sources) do
|
|
111
|
+
described_class.to_models(
|
|
112
|
+
File.read("spec/fixtures/xml/schema/rnc/book_features.rnc"),
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "accepts compatibility annotations before attributes" do
|
|
117
|
+
expect(sources["Book"]).to include("class Book")
|
|
118
|
+
expect(sources["Book"]).to include('map_attribute "format", to: :format')
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "treats an attribute choice mixing text with a fixed value as a string" do
|
|
122
|
+
expect(sources["Book"]).to include("attribute :indent, :string")
|
|
123
|
+
expect(sources["Book"]).to include('map_attribute "indent", to: :indent')
|
|
124
|
+
expect(sources.keys).not_to include("IndentType")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it "preserves xml-prefixed attributes in the XML mapping" do
|
|
128
|
+
expect(sources["Book"]).to include("attribute :xml_lang")
|
|
129
|
+
expect(sources["Book"]).to include('map_attribute "xml:lang", to: :xml_lang')
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it "normalizes escaped identifiers into generated class names" do
|
|
133
|
+
expect(sources.keys).to include("List")
|
|
134
|
+
expect(sources["Book"]).to include("attribute :list, List")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it "loads generated classes that parse normalized RNC mappings" do
|
|
138
|
+
stub_const("RncFeatureSpec", Module.new)
|
|
139
|
+
described_class.to_models(
|
|
140
|
+
File.read("spec/fixtures/xml/schema/rnc/book_features.rnc"),
|
|
141
|
+
load_classes: true,
|
|
142
|
+
module_namespace: "RncFeatureSpec",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
xml = <<~XML
|
|
146
|
+
<book xml:lang="en" indent="adaptive">
|
|
147
|
+
<title>Example</title>
|
|
148
|
+
<list>
|
|
149
|
+
<item>First</item>
|
|
150
|
+
<item>Second</item>
|
|
151
|
+
</list>
|
|
152
|
+
</book>
|
|
153
|
+
XML
|
|
154
|
+
|
|
155
|
+
book = RncFeatureSpec::Book.from_xml(xml)
|
|
156
|
+
expect(book.xml_lang).to eq("en")
|
|
157
|
+
expect(book.indent).to eq("adaptive")
|
|
158
|
+
expect(book.title.content).to eq("Example")
|
|
159
|
+
expect(book.list.item.map(&:content)).to eq(%w[First Second])
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it "does not rewrite attributes with nested datatype parameter braces" do
|
|
163
|
+
source = <<~RNC
|
|
164
|
+
root =
|
|
165
|
+
element root {
|
|
166
|
+
attribute code { xsd:string { pattern = "[A-Z]+" } }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
start |= root
|
|
170
|
+
RNC
|
|
171
|
+
|
|
172
|
+
sources = described_class.to_models(source)
|
|
173
|
+
|
|
174
|
+
expect(sources.keys).to include("Root", "CodeType")
|
|
175
|
+
expect(sources["Root"]).to include("attribute :code, :code_type")
|
|
176
|
+
# The `[A-Z]` inside the string literal must survive: if annotation
|
|
177
|
+
# stripping wrongly treated `[` in the string as a bracket annotation,
|
|
178
|
+
# the pattern would be corrupted.
|
|
179
|
+
expect(sources["CodeType"]).to include("[A-Z]+")
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
context "with include resolution through the location option" do
|
|
184
|
+
let(:path) do
|
|
185
|
+
"spec/fixtures/xml/schema/rnc/includes/library.rnc"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it "loads included RNC definitions before delegating to RngCompiler" do
|
|
189
|
+
sources = described_class.to_models(File.read(path), location: path)
|
|
190
|
+
|
|
191
|
+
expect(sources.keys).to include("Library", "Book", "Title")
|
|
192
|
+
expect(sources["Library"]).to include("attribute :book, Book")
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it "raises a clear error for missing include files" do
|
|
196
|
+
expect do
|
|
197
|
+
described_class.to_models(
|
|
198
|
+
'include "missing.rnc"',
|
|
199
|
+
location: "spec/fixtures/xml/schema/rnc/includes",
|
|
200
|
+
)
|
|
201
|
+
end.to raise_error(/missing\.rnc/)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it "raises a clear error for circular includes" do
|
|
205
|
+
circular_path =
|
|
206
|
+
"spec/fixtures/xml/schema/rnc/includes/circular_a.rnc"
|
|
207
|
+
|
|
208
|
+
expect do
|
|
209
|
+
described_class.to_models(File.read(circular_path), location: circular_path)
|
|
210
|
+
end.to raise_error(/circular_a\.rnc/)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
context "with load_classes mode" do
|
|
215
|
+
let!(:sources) do
|
|
216
|
+
stub_const("RncLoadClassesSpec", Module.new)
|
|
217
|
+
described_class.to_models(
|
|
218
|
+
File.read("spec/fixtures/xml/schema/rnc/address_book.rnc"),
|
|
219
|
+
load_classes: true,
|
|
220
|
+
module_namespace: "RncLoadClassesSpec",
|
|
221
|
+
)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it "returns generated source and loads constants" do
|
|
225
|
+
expect(sources.keys).to contain_exactly(
|
|
226
|
+
"AddressBook", "Card", "Name", "Email"
|
|
227
|
+
)
|
|
228
|
+
expect(defined?(RncLoadClassesSpec::AddressBook)).to eq("constant")
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
context "with input text and a file location" do
|
|
233
|
+
# Regression for the SourceResolver override bug: when caller passed
|
|
234
|
+
# both `input` and `location:` pointing at a file, the resolver
|
|
235
|
+
# silently read the file from disk and discarded the input.
|
|
236
|
+
it "uses the caller-provided text, not the file contents" do
|
|
237
|
+
Dir.mktmpdir do |dir|
|
|
238
|
+
path = File.join(dir, "real_file.rnc")
|
|
239
|
+
File.write(path, "start = element from_disk { text }")
|
|
240
|
+
inline = "start = element from_inline { text }"
|
|
241
|
+
|
|
242
|
+
sources = described_class.to_models(inline, location: path)
|
|
243
|
+
|
|
244
|
+
expect(sources.keys).to include("FromInline")
|
|
245
|
+
expect(sources.keys).not_to include("FromDisk")
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
context "with an attribute in a foreign namespace" do
|
|
251
|
+
let(:source) do
|
|
252
|
+
%(namespace ex = "urn:test"\n) +
|
|
253
|
+
%(start = element root { attribute ex:code { text } })
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
it "generates a namespaced value type for the attribute" do
|
|
257
|
+
sources = described_class.to_models(source)
|
|
258
|
+
|
|
259
|
+
code_type = sources.keys.find { |k| k.include?("Type") }
|
|
260
|
+
expect(code_type).not_to be_nil
|
|
261
|
+
expect(sources[code_type]).to match(/namespace \w+Namespace/)
|
|
262
|
+
expect(sources[code_type]).to match(/require_relative ".*namespace"/)
|
|
263
|
+
expect(sources["Root"]).to include('map_attribute "code", to: :code')
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it "round-trips the foreign-namespace attribute (parse + fresh serialize)" do
|
|
267
|
+
stub_const("RncForeignNs", Module.new)
|
|
268
|
+
described_class.to_models(
|
|
269
|
+
source, load_classes: true, module_namespace: "RncForeignNs"
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
parsed = RncForeignNs::Root.from_xml(%(<root xmlns:ex="urn:test" ex:code="q"/>))
|
|
273
|
+
expect(parsed.code).to eq("q")
|
|
274
|
+
|
|
275
|
+
# The attribute round-trips in its namespace; the emitted prefix is a
|
|
276
|
+
# generated one (rng does not expose the source prefix), so match the
|
|
277
|
+
# URI and a namespace-qualified `code` attribute rather than "ex".
|
|
278
|
+
out = RncForeignNs::Root.new(code: "f").to_xml
|
|
279
|
+
expect(out).to match(/xmlns:\w+="urn:test"/)
|
|
280
|
+
expect(out).to match(/\w+:code="f"/)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
it "does not namespace a type shared with an unqualified member" do
|
|
284
|
+
sources = described_class.to_models(<<~RNC)
|
|
285
|
+
namespace ex = "urn:test"
|
|
286
|
+
codeType = xsd:string { maxLength = "5" }
|
|
287
|
+
start = element root {
|
|
288
|
+
attribute plain { codeType },
|
|
289
|
+
attribute ex:tagged { codeType }
|
|
290
|
+
}
|
|
291
|
+
RNC
|
|
292
|
+
|
|
293
|
+
# The shared codeType must NOT gain a namespace (plain uses it
|
|
294
|
+
# unqualified); the foreign attribute gets its own namespaced type.
|
|
295
|
+
expect(sources["CodeType"]).not_to match(/namespace \w+Namespace/)
|
|
296
|
+
namespaced = sources.keys.find do |k|
|
|
297
|
+
k != "DefaultNamespace" && sources[k].match?(/namespace \w+Namespace/)
|
|
298
|
+
end
|
|
299
|
+
expect(namespaced).not_to be_nil
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
it "namespaces a foreign-ns attribute with inline facets via a subclass" do
|
|
303
|
+
sources = described_class.to_models(
|
|
304
|
+
%(namespace ex = "urn:test"\n) +
|
|
305
|
+
%(start = element root { attribute ex:code { xsd:string { maxLength = "5" } } }),
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# The inline restricted type is subclassed (not mutated) so a fresh
|
|
309
|
+
# namespaced type inherits its constraints and carries the namespace.
|
|
310
|
+
expect(sources["CodeType"]).not_to match(/namespace \w+Namespace/)
|
|
311
|
+
subclass = sources.keys.find { |k| sources[k].match?(/class \w+ < CodeType/) }
|
|
312
|
+
expect(subclass).not_to be_nil
|
|
313
|
+
expect(sources[subclass]).to match(/namespace \w+Namespace/)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
it "keeps distinct URIs and prefixes when their generated names collide" do
|
|
317
|
+
sources = described_class.to_models(<<~RNC)
|
|
318
|
+
namespace x = "urn:a"
|
|
319
|
+
namespace y = "urn:b"
|
|
320
|
+
start = element root { attribute x:one { text }, attribute y:two { text } }
|
|
321
|
+
RNC
|
|
322
|
+
|
|
323
|
+
namespaces = sources.select { |_, v| v.include?("Lutaml::Xml::W3c::XmlNamespace") }
|
|
324
|
+
expect(namespaces.size).to eq(2)
|
|
325
|
+
uris = namespaces.values.map { |v| v[/uri "([^"]+)"/, 1] }
|
|
326
|
+
prefixes = namespaces.values.map { |v| v[/prefix_default "([^"]+)"/, 1] }
|
|
327
|
+
expect(uris).to contain_exactly("urn:a", "urn:b")
|
|
328
|
+
expect(prefixes.uniq.size).to eq(2)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
it "does not let a define shadow a built-in type of the same name" do
|
|
332
|
+
# `element pre { dateTime }` forces the custom `dateTime` define into
|
|
333
|
+
# the class set BEFORE the foreign-ns attribute resolves, so the guard
|
|
334
|
+
# is actually exercised: the attribute's primitive xsd:dateTime must
|
|
335
|
+
# still subclass the built-in DateTime, not the custom `dateTime`.
|
|
336
|
+
sources = described_class.to_models(<<~RNC)
|
|
337
|
+
dateTime = xsd:string { pattern = "custom" }
|
|
338
|
+
start = element root {
|
|
339
|
+
element pre { dateTime },
|
|
340
|
+
attribute ex:when { xsd:dateTime }
|
|
341
|
+
}
|
|
342
|
+
RNC
|
|
343
|
+
|
|
344
|
+
wrapper = sources.keys.find { |k| sources[k].match?(/namespace \w+Namespace/) && !k.include?("Namespace") }
|
|
345
|
+
expect(sources[wrapper]).to include("< Lutaml::Model::Type::DateTime")
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
context "with namespaced root and child elements" do
|
|
350
|
+
let(:source) do
|
|
351
|
+
%(namespace ex = "urn:test"\n) +
|
|
352
|
+
%(start = element ex:root { element ex:child { text } })
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
it "declares a class-level namespace with qualified element form" do
|
|
356
|
+
sources = described_class.to_models(source)
|
|
357
|
+
ns_key = sources.keys.find { |k| k.include?("Namespace") }
|
|
358
|
+
|
|
359
|
+
expect(sources["Root"]).to match(/namespace \w+Namespace/)
|
|
360
|
+
expect(sources[ns_key]).to include('uri "urn:test"')
|
|
361
|
+
expect(sources[ns_key]).to include("element_form_default :qualified")
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
it "round-trips root and child in their namespace" do
|
|
365
|
+
stub_const("RncNsElem", Module.new)
|
|
366
|
+
described_class.to_models(
|
|
367
|
+
source, load_classes: true, module_namespace: "RncNsElem"
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
xml = %(<ex:root xmlns:ex="urn:test"><ex:child>hi</ex:child></ex:root>)
|
|
371
|
+
expect(RncNsElem::Root.from_xml(xml).child).to eq("hi")
|
|
372
|
+
|
|
373
|
+
out = RncNsElem::Root.new(child: "x").to_xml
|
|
374
|
+
expect(out).to match(/xmlns(:\w+)?="urn:test"/)
|
|
375
|
+
expect(out).not_to include('xmlns=""')
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
context "with bracket annotations handled natively by rng" do
|
|
380
|
+
it "compiles an annotation before a non-leading definition" do
|
|
381
|
+
sources = described_class.to_models(<<~RNC)
|
|
382
|
+
foo = element foo { text }
|
|
383
|
+
[ a:defaultValue = "x" ]
|
|
384
|
+
bar = element bar { text }
|
|
385
|
+
start = element root { foo, bar }
|
|
386
|
+
RNC
|
|
387
|
+
|
|
388
|
+
expect(sources.keys).to include("Root", "Foo", "Bar")
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
context "via Schema.from_rnc entry point" do
|
|
393
|
+
it "delegates through the registered XML schema method" do
|
|
394
|
+
result = Lutaml::Model::Schema.from_rnc(
|
|
395
|
+
File.read("spec/fixtures/xml/schema/rnc/address_book.rnc"),
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
expect(result.keys).to contain_exactly(
|
|
399
|
+
"AddressBook", "Card", "Name", "Email"
|
|
400
|
+
)
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
end
|