lutaml-model 0.8.17 → 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/.gitmodules +6 -0
- data/.rubocop_todo.yml +8 -0
- 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-mapping.adoc +21 -0
- data/docs/_pages/validation.adoc +65 -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/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/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 +18 -0
- data/lib/lutaml/toml.rb +1 -1
- data/lib/lutaml/xml/adapter.rb +1 -1
- 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 +21 -8
- 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/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/model/attribute_apply_value_map_spec.rb +134 -0
- data/spec/lutaml/model/attribute_spec.rb +169 -12
- data/spec/lutaml/model/boot_manifest_spec.rb +30 -0
- data/spec/lutaml/model/collection_spec.rb +18 -0
- data/spec/lutaml/model/mixed_content_spec.rb +171 -0
- data/spec/lutaml/model/opal_smoke_spec.rb +67 -2
- data/spec/lutaml/model/ordered_content_spec.rb +30 -0
- 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/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/xml/opal_xml_spec.rb +22 -20
- 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/validate_xml_with_constructs_spec.rb +222 -0
- data/spec/lutaml/xml/validate_xml_with_spec.rb +186 -0
- data/spec/spec_helper.rb +28 -11
- data/spec/support/opal.rb +5 -2
- metadata +97 -31
- 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
|
@@ -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
|