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,191 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module XmlCompiler
|
|
7
|
+
class SpecBuilder
|
|
8
|
+
# Builds Definitions::Model specs for XSD <complexType>, named
|
|
9
|
+
# <group>, and <attributeGroup> nodes, plus the SimpleContent
|
|
10
|
+
# sidecar.
|
|
11
|
+
#
|
|
12
|
+
# Reads from the parent SpecBuilder:
|
|
13
|
+
# - @parent.attribute_groups for ref resolution
|
|
14
|
+
# - @parent.group_types as the destination for anonymous
|
|
15
|
+
# named groups discovered while walking complexType bodies
|
|
16
|
+
# - @parent.namespace_class_name to stamp on built Models
|
|
17
|
+
# Delegates leaf-member building (attribute/element/sequence/
|
|
18
|
+
# choice) back to @parent.members for shared resolution rules.
|
|
19
|
+
class ComplexTypes
|
|
20
|
+
def initialize(parent)
|
|
21
|
+
@parent = parent
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# <xs:complexType> -> Definitions::Model.
|
|
25
|
+
def build(complex_type)
|
|
26
|
+
model = Definitions::Model.new(
|
|
27
|
+
class_name: Utils.camel_case(complex_type.name),
|
|
28
|
+
xml_root: Definitions::XmlRoot.new(kind: :element, name: complex_type.name),
|
|
29
|
+
mixed: !!complex_type.mixed,
|
|
30
|
+
namespace_class_name: @parent.namespace_class_name,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
ElementOrder.resolved(complex_type).each do |element|
|
|
34
|
+
add_child(model, element)
|
|
35
|
+
end
|
|
36
|
+
model
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# <xs:group> at top level -> Definitions::Model with
|
|
40
|
+
# module_wrappable: false. Used by both the registry walker
|
|
41
|
+
# and by `add_group_to_model` when it discovers a named-but-
|
|
42
|
+
# not-yet-registered anonymous group.
|
|
43
|
+
def build_group(group)
|
|
44
|
+
inner_members = []
|
|
45
|
+
if (built = build_group_inner(group))
|
|
46
|
+
inner_members << built
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
base_name = group.name || Utils.last_of_split(group.ref)
|
|
50
|
+
Definitions::Model.new(
|
|
51
|
+
class_name: Utils.camel_case(base_name),
|
|
52
|
+
xml_root: Definitions::XmlRoot.new(kind: :type_name, name: base_name),
|
|
53
|
+
members: inner_members,
|
|
54
|
+
module_wrappable: false,
|
|
55
|
+
lazy_register: true,
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# <xs:attributeGroup> -> Array of Definitions::Attribute. Top
|
|
60
|
+
# level groups are registered for ref resolution; inline ones
|
|
61
|
+
# are flattened directly into a Model's members.
|
|
62
|
+
def build_attribute_group(attribute_group)
|
|
63
|
+
ref = attribute_group.ref
|
|
64
|
+
if ref && !attribute_group.name
|
|
65
|
+
target = @parent.attribute_groups[Utils.last_of_split(ref)]
|
|
66
|
+
return Array(target)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
collect_attributes(attribute_group)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# <xs:simpleContent> -> Definitions::SimpleContent sidecar.
|
|
73
|
+
def build_simple_content(simple_content)
|
|
74
|
+
additional = []
|
|
75
|
+
base_class = nil
|
|
76
|
+
if simple_content.extension
|
|
77
|
+
ext = simple_content.extension
|
|
78
|
+
base_class = ext.base
|
|
79
|
+
additional = collect_attributes(ext)
|
|
80
|
+
elsif simple_content.restriction
|
|
81
|
+
base_class = simple_content.restriction.base
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
Definitions::SimpleContent.new(
|
|
85
|
+
base_class: base_class,
|
|
86
|
+
additional_attributes: additional,
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
# Flattens the <attribute> / <attributeGroup> children of
|
|
93
|
+
# `container` into an array of Definitions::Attribute.
|
|
94
|
+
def collect_attributes(container)
|
|
95
|
+
members = []
|
|
96
|
+
ElementOrder.resolved(container).each do |item|
|
|
97
|
+
case item
|
|
98
|
+
when Lutaml::Xml::Schema::Xsd::Attribute
|
|
99
|
+
attr = @parent.members_builder.build_attribute(item)
|
|
100
|
+
members << attr if attr
|
|
101
|
+
when Lutaml::Xml::Schema::Xsd::AttributeGroup
|
|
102
|
+
members.concat(build_attribute_group(item))
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
members
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def add_child(model, element)
|
|
109
|
+
case element
|
|
110
|
+
when Lutaml::Xml::Schema::Xsd::Attribute
|
|
111
|
+
attr = @parent.members_builder.build_attribute(element)
|
|
112
|
+
model.members << attr if attr
|
|
113
|
+
when Lutaml::Xml::Schema::Xsd::Sequence
|
|
114
|
+
model.members << @parent.members_builder.build_sequence(element)
|
|
115
|
+
when Lutaml::Xml::Schema::Xsd::Choice
|
|
116
|
+
model.members << @parent.members_builder.build_choice(element)
|
|
117
|
+
when Lutaml::Xml::Schema::Xsd::ComplexContent
|
|
118
|
+
apply_complex_content(element, model)
|
|
119
|
+
when Lutaml::Xml::Schema::Xsd::AttributeGroup
|
|
120
|
+
model.members.concat(build_attribute_group(element))
|
|
121
|
+
when Lutaml::Xml::Schema::Xsd::Group
|
|
122
|
+
add_group_to_model(element, model)
|
|
123
|
+
when Lutaml::Xml::Schema::Xsd::SimpleContent
|
|
124
|
+
model.simple_content = build_simple_content(element)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def apply_complex_content(content, model)
|
|
129
|
+
model.mixed = true if content.mixed
|
|
130
|
+
|
|
131
|
+
if (ext = content.extension)
|
|
132
|
+
model.parent_class = qualified_class(ext.base)
|
|
133
|
+
ElementOrder.resolved(ext).each { |c| add_child(model, c) }
|
|
134
|
+
elsif (res = content.restriction)
|
|
135
|
+
model.parent_class = qualified_class(res.base)
|
|
136
|
+
ElementOrder.resolved(res).each do |c|
|
|
137
|
+
# XSD: restrictions on complex content inherit
|
|
138
|
+
# sequence/choice/group from base.
|
|
139
|
+
next if c.is_a?(Lutaml::Xml::Schema::Xsd::Sequence) ||
|
|
140
|
+
c.is_a?(Lutaml::Xml::Schema::Xsd::Choice) ||
|
|
141
|
+
c.is_a?(Lutaml::Xml::Schema::Xsd::Group)
|
|
142
|
+
|
|
143
|
+
add_child(model, c)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def qualified_class(raw)
|
|
149
|
+
return "Lutaml::Model::Serializable" if raw.nil?
|
|
150
|
+
|
|
151
|
+
Utils.camel_case(Utils.last_of_split(raw))
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def add_group_to_model(group, model)
|
|
155
|
+
# Anonymous group OR named-but-not-referenced group: unwrap
|
|
156
|
+
# the inner sequence/choice inline into the model. (Matches
|
|
157
|
+
# the old XmlCompiler::Group rendering, which only emitted
|
|
158
|
+
# import_model_attributes/mappings when `ref` was set.)
|
|
159
|
+
if group.ref.nil?
|
|
160
|
+
add_anonymous_group_contents(group, model)
|
|
161
|
+
if group.name && !@parent.group_types.key?(group.name)
|
|
162
|
+
@parent.group_types[group.name] = build_group(group)
|
|
163
|
+
end
|
|
164
|
+
return
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
model.members << Definitions::GroupImport.new(
|
|
168
|
+
name: Utils.snake_case(Utils.last_of_split(group.ref)),
|
|
169
|
+
)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def add_anonymous_group_contents(group, model)
|
|
173
|
+
built = build_group_inner(group)
|
|
174
|
+
model.members << built if built
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def build_group_inner(group)
|
|
178
|
+
inner = group.sequence || group.choice
|
|
179
|
+
return nil unless inner
|
|
180
|
+
|
|
181
|
+
case inner
|
|
182
|
+
when Lutaml::Xml::Schema::Xsd::Sequence then @parent.members_builder.build_sequence(inner)
|
|
183
|
+
when Lutaml::Xml::Schema::Xsd::Choice then @parent.members_builder.build_choice(inner)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module XmlCompiler
|
|
7
|
+
class SpecBuilder
|
|
8
|
+
# Builds the leaf member specs that live inside generated
|
|
9
|
+
# classes: Attribute / Element (both surface as
|
|
10
|
+
# Definitions::Attribute with different `kind:` values),
|
|
11
|
+
# Sequence, Choice, GroupImport, plus the TypeRef wrapper.
|
|
12
|
+
#
|
|
13
|
+
# Holds @parent (SpecBuilder) so it can:
|
|
14
|
+
# - look up @parent.attributes / @parent.elements for refs
|
|
15
|
+
# - write anonymous simple/complex types into @parent's hashes
|
|
16
|
+
# - delegate complex-type building back to @parent for nested
|
|
17
|
+
# anonymous types
|
|
18
|
+
class Members
|
|
19
|
+
def initialize(parent)
|
|
20
|
+
@parent = parent
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# ----- Attributes ------------------------------------------------
|
|
24
|
+
|
|
25
|
+
def build_attribute(attr)
|
|
26
|
+
return resolve_attribute_ref(attr) if attr.ref && !attr.name
|
|
27
|
+
|
|
28
|
+
Definitions::Attribute.new(
|
|
29
|
+
name: Utils.snake_case(attr.name),
|
|
30
|
+
type: build_type_ref(resolve_attribute_type(attr)),
|
|
31
|
+
xml_name: attr.name,
|
|
32
|
+
kind: :attribute,
|
|
33
|
+
default: attr.default,
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def build_top_level_attribute(item, kind:)
|
|
38
|
+
return item unless item.respond_to?(:name)
|
|
39
|
+
|
|
40
|
+
type_str = top_level_type_str(item)
|
|
41
|
+
Definitions::Attribute.new(
|
|
42
|
+
name: Utils.snake_case(item.name.to_s),
|
|
43
|
+
type: build_type_ref(type_str || "string"),
|
|
44
|
+
xml_name: item.name.to_s,
|
|
45
|
+
kind: kind,
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# ----- Elements --------------------------------------------------
|
|
50
|
+
|
|
51
|
+
def build_element(element)
|
|
52
|
+
return resolve_element_ref(element) if element.ref && !element.name
|
|
53
|
+
|
|
54
|
+
Definitions::Attribute.new(
|
|
55
|
+
name: Utils.snake_case(element.name),
|
|
56
|
+
type: build_type_ref(resolve_element_type(element)),
|
|
57
|
+
xml_name: element.name,
|
|
58
|
+
kind: :element,
|
|
59
|
+
collection: collection_from_occurs(element.min_occurs, element.max_occurs),
|
|
60
|
+
default: element.default,
|
|
61
|
+
render_default: !element.default.nil?,
|
|
62
|
+
render_empty: element_required?(element.min_occurs),
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# ----- Sequence / Choice -----------------------------------------
|
|
67
|
+
|
|
68
|
+
def build_sequence(sequence)
|
|
69
|
+
members = ElementOrder.resolved(sequence).filter_map do |item|
|
|
70
|
+
next if item.is_a?(Lutaml::Xml::Schema::Xsd::Any)
|
|
71
|
+
|
|
72
|
+
build_member(item)
|
|
73
|
+
end
|
|
74
|
+
Definitions::Sequence.new(members: members)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def build_choice(choice)
|
|
78
|
+
alternatives = ElementOrder.resolved(choice).filter_map { |item| build_member(item) }
|
|
79
|
+
Definitions::Choice.new(
|
|
80
|
+
alternatives: alternatives,
|
|
81
|
+
header: choice_header(choice),
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# A `<xs:group ref="..."/>` appearing inside a sequence/choice
|
|
86
|
+
# becomes an inline import directive. Anonymous in-place groups
|
|
87
|
+
# (no name, no ref) are flattened by the complex-type walker
|
|
88
|
+
# before they reach this method.
|
|
89
|
+
def build_group_member(group)
|
|
90
|
+
return nil if group.ref.nil?
|
|
91
|
+
|
|
92
|
+
Definitions::GroupImport.new(
|
|
93
|
+
name: Utils.snake_case(Utils.last_of_split(group.ref)),
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# ----- TypeRef ---------------------------------------------------
|
|
98
|
+
|
|
99
|
+
def build_type_ref(raw_type)
|
|
100
|
+
return Definitions::TypeRef.new(kind: :symbol, value: "string") if raw_type.nil?
|
|
101
|
+
return Definitions::TypeRef.new(kind: :w3c, value: raw_type) if w3c_type?(raw_type)
|
|
102
|
+
|
|
103
|
+
local = Utils.last_of_split(raw_type)
|
|
104
|
+
Definitions::TypeRef.new(kind: :symbol, value: Utils.snake_case(local))
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def build_member(item)
|
|
110
|
+
case item
|
|
111
|
+
when Lutaml::Xml::Schema::Xsd::Sequence then build_sequence(item)
|
|
112
|
+
when Lutaml::Xml::Schema::Xsd::Element then build_element(item)
|
|
113
|
+
when Lutaml::Xml::Schema::Xsd::Choice then build_choice(item)
|
|
114
|
+
when Lutaml::Xml::Schema::Xsd::Group then build_group_member(item)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def resolve_attribute_ref(attr)
|
|
119
|
+
target = @parent.attributes[Utils.last_of_split(attr.ref)]
|
|
120
|
+
return nil unless target
|
|
121
|
+
|
|
122
|
+
Definitions::Attribute.new(
|
|
123
|
+
name: target.name,
|
|
124
|
+
type: target.type,
|
|
125
|
+
xml_name: target.xml_name,
|
|
126
|
+
kind: :attribute,
|
|
127
|
+
default: target.default,
|
|
128
|
+
)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def resolve_attribute_type(attr)
|
|
132
|
+
return attr.type if attr.type
|
|
133
|
+
|
|
134
|
+
register_anonymous_simple_type("ST_#{attr.name}", attr.simple_type)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def resolve_element_ref(element)
|
|
138
|
+
target = @parent.elements[Utils.last_of_split(element.ref)]
|
|
139
|
+
return nil unless target
|
|
140
|
+
|
|
141
|
+
Definitions::Attribute.new(
|
|
142
|
+
name: target.name,
|
|
143
|
+
type: target.type,
|
|
144
|
+
xml_name: target.xml_name,
|
|
145
|
+
kind: :element,
|
|
146
|
+
collection: collection_from_occurs(element.min_occurs, element.max_occurs),
|
|
147
|
+
default: target.default,
|
|
148
|
+
render_default: !target.default.nil?,
|
|
149
|
+
render_empty: element_required?(element.min_occurs),
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def resolve_element_type(element)
|
|
154
|
+
return element.type if element.type
|
|
155
|
+
return register_anonymous_simple_type("ST_#{element.name}", element.simple_type) if element.simple_type
|
|
156
|
+
return register_anonymous_complex_type("CT_#{element.name}", element.complex_type) if element.complex_type
|
|
157
|
+
|
|
158
|
+
"string"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def top_level_type_str(item)
|
|
162
|
+
return item.type if item.respond_to?(:type) && item.type
|
|
163
|
+
return register_anonymous_simple_type("ST_#{item.name}", item.simple_type) if item.respond_to?(:simple_type) && item.simple_type
|
|
164
|
+
|
|
165
|
+
register_anonymous_complex_type("CT_#{item.name}", item.complex_type) if item.respond_to?(:complex_type) && item.complex_type
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def register_anonymous_simple_type(anon_name, anon_node)
|
|
169
|
+
anon_node.name = anon_name
|
|
170
|
+
@parent.simple_types[anon_name] = @parent.build_simple_type(anon_node)
|
|
171
|
+
anon_name
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def register_anonymous_complex_type(anon_name, anon_node)
|
|
175
|
+
anon_node.name = anon_name
|
|
176
|
+
@parent.complex_types[anon_name] = @parent.build_complex_type(anon_node)
|
|
177
|
+
anon_name
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def collection_from_occurs(min_occurs, max_occurs)
|
|
181
|
+
return false if min_occurs.nil? && max_occurs.nil?
|
|
182
|
+
|
|
183
|
+
min = min_occurs.nil? ? 1 : min_occurs.to_i
|
|
184
|
+
max = case max_occurs
|
|
185
|
+
when "unbounded" then Float::INFINITY
|
|
186
|
+
when NilClass then 1
|
|
187
|
+
else max_occurs.to_i
|
|
188
|
+
end
|
|
189
|
+
return false if min == 1 && max == 1
|
|
190
|
+
|
|
191
|
+
(min..max)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def element_required?(min_occurs)
|
|
195
|
+
min_occurs.nil? || min_occurs.to_i >= 1
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def choice_header(choice)
|
|
199
|
+
min = choice.min_occurs.nil? ? 1 : choice.min_occurs.to_i
|
|
200
|
+
max = case choice.max_occurs
|
|
201
|
+
when "unbounded" then "Float::INFINITY"
|
|
202
|
+
when NilClass then 1
|
|
203
|
+
else choice.max_occurs.to_i
|
|
204
|
+
end
|
|
205
|
+
"choice(min: #{min}, max: #{max})"
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def w3c_type?(raw_type)
|
|
209
|
+
raw_type.to_s.start_with?("Lutaml::Xml::W3c::")
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module XmlCompiler
|
|
7
|
+
class SpecBuilder
|
|
8
|
+
# Builds Definitions::RestrictedType / Definitions::UnionType
|
|
9
|
+
# specs from XSD <xs:simpleType> AST nodes, including the
|
|
10
|
+
# facet container that lives on the restricted type.
|
|
11
|
+
#
|
|
12
|
+
# Stateless. XSD built-in metadata comes from
|
|
13
|
+
# XmlCompiler::SupportedDataTypes; the back-reference to the
|
|
14
|
+
# parent SpecBuilder is kept for symmetry with other sub-builders.
|
|
15
|
+
class SimpleTypes
|
|
16
|
+
def initialize(parent)
|
|
17
|
+
@parent = parent
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def build(simple_type)
|
|
21
|
+
if (union = simple_type.union)
|
|
22
|
+
build_union_type(simple_type.name, union.member_types.split)
|
|
23
|
+
else
|
|
24
|
+
build_restricted_type(simple_type.name, simple_type.restriction)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def build_supported(name, info)
|
|
29
|
+
base = Utils.base_class_snake_case(info[:class_name])
|
|
30
|
+
validations = info[:validations] || {}
|
|
31
|
+
facet = Definitions::Facet.new(
|
|
32
|
+
min_inclusive: validations[:min_inclusive],
|
|
33
|
+
max_inclusive: validations[:max_inclusive],
|
|
34
|
+
pattern: validations[:pattern],
|
|
35
|
+
)
|
|
36
|
+
transform = validations[:transform] &&
|
|
37
|
+
Definitions::TransformFacet.new(expression: validations[:transform])
|
|
38
|
+
|
|
39
|
+
Definitions::RestrictedType.new(
|
|
40
|
+
class_name: Utils.camel_case(name),
|
|
41
|
+
parent_class: restricted_parent_class(base),
|
|
42
|
+
facets: facet,
|
|
43
|
+
transform_facet: transform,
|
|
44
|
+
required_files: supported_required_files(base),
|
|
45
|
+
keep_register_when_namespaced: true,
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def build_union_type(name, member_type_names)
|
|
52
|
+
type_refs = member_type_names.map do |raw|
|
|
53
|
+
snake = Utils.snake_case(Utils.last_of_split(raw))
|
|
54
|
+
Definitions::TypeRef.new(kind: :symbol, value: snake)
|
|
55
|
+
end
|
|
56
|
+
Definitions::UnionType.new(
|
|
57
|
+
class_name: Utils.camel_case(name),
|
|
58
|
+
members: type_refs,
|
|
59
|
+
cast_strategy: :resolve_type,
|
|
60
|
+
required_files: union_required_files(member_type_names),
|
|
61
|
+
lazy_register: true,
|
|
62
|
+
keep_register_when_namespaced: true,
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def union_required_files(member_type_names)
|
|
67
|
+
member_type_names.filter_map do |raw|
|
|
68
|
+
local = Utils.last_of_split(raw)
|
|
69
|
+
next if skippable?(local)
|
|
70
|
+
|
|
71
|
+
%(require_relative "#{Utils.snake_case(local)}")
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def build_restricted_type(name, restriction)
|
|
76
|
+
restriction_base = restriction&.base
|
|
77
|
+
base_class = restriction_base&.split(":")&.last
|
|
78
|
+
facet = restriction ? build_facet(restriction) : Definitions::Facet.new
|
|
79
|
+
|
|
80
|
+
Definitions::RestrictedType.new(
|
|
81
|
+
class_name: Utils.camel_case(name),
|
|
82
|
+
parent_class: restricted_parent_class(base_class),
|
|
83
|
+
facets: facet,
|
|
84
|
+
transform_facet: nil,
|
|
85
|
+
required_files: restricted_required_files(base_class),
|
|
86
|
+
keep_register_when_namespaced: true,
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def restricted_parent_class(base_class)
|
|
91
|
+
type_info = SupportedDataTypes[base_class&.to_sym]
|
|
92
|
+
return type_info[:class_name] if type_info&.dig(:skippable)
|
|
93
|
+
return Utils.camel_case(base_class.to_s) if !type_info&.dig(:skippable) && Utils.present?(base_class)
|
|
94
|
+
|
|
95
|
+
"Lutaml::Model::Type::Value"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def restricted_required_files(base_class)
|
|
99
|
+
return [] if Utils.blank?(base_class)
|
|
100
|
+
|
|
101
|
+
return [%(require "bigdecimal")] if base_class == "decimal"
|
|
102
|
+
return [] if SupportedDataTypes.skippable?(base_class)
|
|
103
|
+
|
|
104
|
+
[%(require_relative "#{Utils.snake_case(base_class)}")]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def supported_required_files(base_class)
|
|
108
|
+
return [] if Utils.blank?(base_class) || SupportedDataTypes.skippable?(base_class)
|
|
109
|
+
|
|
110
|
+
[%(require_relative "#{Utils.snake_case(base_class)}")]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def skippable?(local)
|
|
114
|
+
SupportedDataTypes.skippable?(local)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# ----- Facets ---------------------------------------------------
|
|
118
|
+
|
|
119
|
+
def build_facet(restriction)
|
|
120
|
+
Definitions::Facet.new(
|
|
121
|
+
max_length: pick_minmax(restriction.max_length, :min),
|
|
122
|
+
min_length: pick_minmax(restriction.min_length, :max),
|
|
123
|
+
min_inclusive: pick_minmax(restriction.min_inclusive, :max),
|
|
124
|
+
max_inclusive: pick_minmax(restriction.max_inclusive, :min),
|
|
125
|
+
max_exclusive: pick_minmax(restriction.max_exclusive, :max),
|
|
126
|
+
min_exclusive: pick_minmax(restriction.min_exclusive, :min),
|
|
127
|
+
length: restriction.length&.any? ? restriction_length(restriction.length) : nil,
|
|
128
|
+
pattern: build_pattern(restriction.pattern),
|
|
129
|
+
enumerations: restriction.enumeration&.any? ? restriction.enumeration.map(&:value) : nil,
|
|
130
|
+
)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def pick_minmax(field_value, method)
|
|
134
|
+
return nil unless field_value&.any?
|
|
135
|
+
|
|
136
|
+
field_value.map(&:value).public_send(method).to_s
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def restriction_length(lengths)
|
|
140
|
+
lengths.map { |l| { value: l.value, fixed: l.fixed } }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def build_pattern(patterns)
|
|
144
|
+
return nil if Utils.blank?(patterns)
|
|
145
|
+
|
|
146
|
+
patterns.map { |p| "(#{p.value})" }.join("|")
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|