lutaml-model 0.8.17 → 0.8.19
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/builder-dsl.adoc +127 -0
- 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 +23 -11
- data/lib/lutaml/model/serialize/builder.rb +70 -2
- data/lib/lutaml/model/serialize/initialization.rb +3 -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 +104 -4
- 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 +41 -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/serialize/builder_spec.rb +358 -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 +99 -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,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module RngCompiler
|
|
7
|
+
# RNG-specific helpers used by ElementVisitor, DefineClassifier, and
|
|
8
|
+
# ValueTypeResolver. Centralising these here avoids the three-way
|
|
9
|
+
# semantic drift the first refactor introduced (e.g. inconsistent
|
|
10
|
+
# `pure_value_choice?` implementations).
|
|
11
|
+
module RngHelpers
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
# True if the compiled define renders as a value type (RestrictedType
|
|
15
|
+
# or UnionType) rather than its own Serializable class. Single
|
|
16
|
+
# source of truth — used by ElementVisitor and ValueTypeResolver.
|
|
17
|
+
def simple_type?(klass)
|
|
18
|
+
klass.is_a?(Definitions::RestrictedType) ||
|
|
19
|
+
klass.is_a?(Definitions::UnionType)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# True iff `klass` is a fragment model (no rooted xml element).
|
|
23
|
+
def fragment_model?(klass)
|
|
24
|
+
klass.is_a?(Definitions::Model) && klass.xml_root.kind == :fragment
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns the only element of a collection, or nil if the
|
|
28
|
+
# collection is empty or has more than one element. Treats nil as
|
|
29
|
+
# an empty collection.
|
|
30
|
+
def single(collection)
|
|
31
|
+
arr = Array(collection)
|
|
32
|
+
arr.size == 1 ? arr.first : nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# An RNG node has structural content when any of its element /
|
|
36
|
+
# ref / attribute / choice / group / optional / repetition
|
|
37
|
+
# children are populated.
|
|
38
|
+
def structural_content?(node)
|
|
39
|
+
node && present_children?(node, STRUCTURAL_CHILD_NAMES)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# True iff any of `attrs` is a populated array-child of `node`.
|
|
43
|
+
def present_children?(node, attrs)
|
|
44
|
+
attrs.any? do |attr|
|
|
45
|
+
node.respond_to?(attr) && Array(node.public_send(attr)).any?
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# True when `node` has branching structure that needs its own
|
|
50
|
+
# class. Pure value-choices and `<ref>`s to simple types are NOT
|
|
51
|
+
# branching — they're typed leaves.
|
|
52
|
+
def branching_structural?(node)
|
|
53
|
+
return false unless node
|
|
54
|
+
|
|
55
|
+
non_leaf = STRUCTURAL_CHILD_NAMES - %i[choice ref]
|
|
56
|
+
return true if present_children?(node, non_leaf)
|
|
57
|
+
|
|
58
|
+
return false unless node.respond_to?(:choice)
|
|
59
|
+
|
|
60
|
+
Array(node.choice).any? { |c| !pure_value_choice?(c) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# True iff `choice` is a <choice> consisting purely of <value>
|
|
64
|
+
# alternatives (no <element>, <ref>, <data>, or <text>). A <text>
|
|
65
|
+
# alternative subsumes the literals, so `text | "x"` is a plain
|
|
66
|
+
# string, not an enum restricted to "x".
|
|
67
|
+
def pure_value_choice?(choice)
|
|
68
|
+
return false unless choice
|
|
69
|
+
return false unless choice.respond_to?(:value) && Array(choice.value).any?
|
|
70
|
+
return false if choice.respond_to?(:text) && Utils.present?(choice.text)
|
|
71
|
+
|
|
72
|
+
!present_children?(choice, %i[element ref data])
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# True iff `choice` is a union: at least 2 <data> alternatives
|
|
76
|
+
# and no other content.
|
|
77
|
+
def pure_union_choice?(choice)
|
|
78
|
+
return false unless choice
|
|
79
|
+
return false unless choice.respond_to?(:data) && Array(choice.data).size >= 2
|
|
80
|
+
|
|
81
|
+
!present_children?(choice, %i[element ref value])
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Build a Definitions::Facet from an RNG <data>'s <param> children.
|
|
85
|
+
def facet_from_data(data)
|
|
86
|
+
facet = Definitions::Facet.new
|
|
87
|
+
Array(data.param).each do |param|
|
|
88
|
+
apply_param(facet, param.name, param.value.to_s)
|
|
89
|
+
end
|
|
90
|
+
facet
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Build a Definitions::Facet from an array of <value> elements.
|
|
94
|
+
def facet_from_values(values)
|
|
95
|
+
Definitions::Facet.new(
|
|
96
|
+
enumerations: Array(values).map(&:value),
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Snake_case symbol used as the registry key for a generated
|
|
101
|
+
# simple-type/union-type/namespace class.
|
|
102
|
+
def type_symbol(class_name)
|
|
103
|
+
Utils.snake_case(class_name).to_sym
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# A class name derived from base_name that does not collide with an
|
|
107
|
+
# existing key in `classes` (appends 2, 3, ... on collision).
|
|
108
|
+
def unique_class_name(classes, base_name)
|
|
109
|
+
return base_name unless classes.key?(base_name)
|
|
110
|
+
|
|
111
|
+
counter = 2
|
|
112
|
+
counter += 1 while classes.key?("#{base_name}#{counter}")
|
|
113
|
+
"#{base_name}#{counter}"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Maps an RNG/XSD base symbol to its Ruby parent class string,
|
|
117
|
+
# defaulting to the string type when unknown.
|
|
118
|
+
def parent_class_for(base_symbol)
|
|
119
|
+
Lutaml::Model::Type::TYPE_CODES.fetch(
|
|
120
|
+
base_symbol, Lutaml::Model::Type::TYPE_CODES[:string]
|
|
121
|
+
).to_s
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def apply_param(facet, name, value)
|
|
125
|
+
case name
|
|
126
|
+
when "minInclusive" then facet.min_inclusive = numeric_or_string(value)
|
|
127
|
+
when "maxInclusive" then facet.max_inclusive = numeric_or_string(value)
|
|
128
|
+
when "minExclusive" then facet.min_exclusive = numeric_or_string(value)
|
|
129
|
+
when "maxExclusive" then facet.max_exclusive = numeric_or_string(value)
|
|
130
|
+
when "minLength" then facet.min_length = value.to_i
|
|
131
|
+
when "maxLength" then facet.max_length = value.to_i
|
|
132
|
+
when "length" then facet.length = value.to_i
|
|
133
|
+
when "pattern" then facet.pattern = value
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def numeric_or_string(value)
|
|
138
|
+
return value.to_i if /\A-?\d+\z/.match?(value)
|
|
139
|
+
return value.to_f if /\A-?\d+\.\d+\z/.match?(value)
|
|
140
|
+
|
|
141
|
+
value
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
STRUCTURAL_CHILD_NAMES = %i[
|
|
145
|
+
element ref attribute choice group
|
|
146
|
+
optional zeroOrMore oneOrMore
|
|
147
|
+
].freeze
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module RngCompiler
|
|
7
|
+
# Resolves the value-shape of an element or attribute container into a
|
|
8
|
+
# Symbol (built-in / registered type) or nil if the container has
|
|
9
|
+
# structural content needing its own class.
|
|
10
|
+
#
|
|
11
|
+
# Patterns handled:
|
|
12
|
+
# <element><text/></element> -> :string
|
|
13
|
+
# <element><data type="X"/></element> -> mapped primitive
|
|
14
|
+
# <element><data ...><param ...>/></data></element> -> anonymous RestrictedType (returns its registry symbol)
|
|
15
|
+
# <element><choice><value>...</value></choice></element> -> anonymous RestrictedType (enum) (returns its registry symbol)
|
|
16
|
+
# <element><ref name="StFoo"/></element> -> :st_foo (if simple)
|
|
17
|
+
#
|
|
18
|
+
# When an anonymous RestrictedType is needed, the resolver *builds* it
|
|
19
|
+
# (pure) and asks the supplied `register_class` callback to register
|
|
20
|
+
# it. Build and register are separate methods so unit testing can
|
|
21
|
+
# exercise the pure logic without side effects.
|
|
22
|
+
class ValueTypeResolver
|
|
23
|
+
def initialize(defines, classes, compile_define:, register_class:)
|
|
24
|
+
@defines = defines
|
|
25
|
+
@classes = classes
|
|
26
|
+
@compile_define = compile_define
|
|
27
|
+
@register_class = register_class
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the resolved type symbol, or nil if `container` needs
|
|
31
|
+
# its own class. May register an anonymous RestrictedType as a
|
|
32
|
+
# side effect (delegated to the register_class callback).
|
|
33
|
+
def resolve(container)
|
|
34
|
+
return nil if RngHelpers.branching_structural?(container)
|
|
35
|
+
|
|
36
|
+
type_from_inline_simple(container) || primitive_or_ref(container)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def type_from_inline_simple(container)
|
|
42
|
+
simple = build_anonymous_simple_type(container)
|
|
43
|
+
return nil unless simple
|
|
44
|
+
|
|
45
|
+
@register_class.call(simple)
|
|
46
|
+
RngHelpers.type_symbol(simple.class_name)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def build_anonymous_simple_type(container)
|
|
50
|
+
anonymous_from_data(container) || anonymous_from_enum_choice(container)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def anonymous_from_data(container)
|
|
54
|
+
data = RngHelpers.single(container.data)
|
|
55
|
+
return nil unless data && Array(data.param).any?
|
|
56
|
+
|
|
57
|
+
base = RngCompiler::DATA_TYPE_MAP.fetch(
|
|
58
|
+
data.type, RngCompiler::DEFAULT_DATA_TYPE
|
|
59
|
+
)
|
|
60
|
+
anonymous_restricted_type(container, base, RngHelpers.facet_from_data(data))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def anonymous_from_enum_choice(container)
|
|
64
|
+
choice = RngHelpers.single(container.choice)
|
|
65
|
+
return nil unless choice && RngHelpers.pure_value_choice?(choice)
|
|
66
|
+
|
|
67
|
+
anonymous_restricted_type(container, :string, RngHelpers.facet_from_values(choice.value))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def anonymous_restricted_type(container, base, facet)
|
|
71
|
+
Definitions::RestrictedType.new(
|
|
72
|
+
class_name: RngHelpers.unique_class_name(
|
|
73
|
+
@classes, "#{Utils.camel_case(container.attr_name.to_s)}Type"
|
|
74
|
+
),
|
|
75
|
+
parent_class: RngHelpers.parent_class_for(base),
|
|
76
|
+
facets: facet,
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def primitive_or_ref(container)
|
|
81
|
+
primitive = detect_primitive_type(container)
|
|
82
|
+
return primitive if primitive
|
|
83
|
+
|
|
84
|
+
ref_to_simple_type_symbol(container)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def ref_to_simple_type_symbol(container)
|
|
88
|
+
ref = RngHelpers.single(container.ref)
|
|
89
|
+
return nil unless ref
|
|
90
|
+
|
|
91
|
+
target = @defines[ref.name]
|
|
92
|
+
return nil unless target
|
|
93
|
+
|
|
94
|
+
target_class = @compile_define.call(target)
|
|
95
|
+
return RngHelpers.type_symbol(target_class.class_name) if RngHelpers.simple_type?(target_class)
|
|
96
|
+
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def detect_primitive_type(child)
|
|
101
|
+
return nil if RngHelpers.structural_content?(child)
|
|
102
|
+
|
|
103
|
+
primitive_from_data(child) || primitive_from_text(child) || primitive_from_value(child)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def primitive_from_data(child)
|
|
107
|
+
return nil unless child.data
|
|
108
|
+
|
|
109
|
+
RngCompiler::DATA_TYPE_MAP.fetch(child.data.type, RngCompiler::DEFAULT_DATA_TYPE)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def primitive_from_text(child)
|
|
113
|
+
:string if Utils.present?(child.text)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def primitive_from_value(child)
|
|
117
|
+
# <element><choice><value>a</value>...</choice></element> falls
|
|
118
|
+
# through to this path as :string with no constraint emitted.
|
|
119
|
+
:string if Array(child.value).any?
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module Model
|
|
7
|
+
module Schema
|
|
8
|
+
# Compiles a parsed RNG grammar into Lutaml::Model::Serializable Ruby
|
|
9
|
+
# source. Mirrors XmlCompiler in API, options, and output format.
|
|
10
|
+
#
|
|
11
|
+
# Requires the `rng` gem (https://github.com/lutaml/rng) at runtime --
|
|
12
|
+
# that gem provides the Rng::Grammar parsing model. lutaml-model itself
|
|
13
|
+
# has no dependency on it. Registration of Schema.from_relaxng is done
|
|
14
|
+
# in lib/lutaml/xml.rb (alongside Schema.from_xml and Schema.to_relaxng).
|
|
15
|
+
#
|
|
16
|
+
# Entry point: Lutaml::Model::Schema::RngCompiler.to_models(rng, options)
|
|
17
|
+
module RngCompiler
|
|
18
|
+
extend self
|
|
19
|
+
|
|
20
|
+
autoload :MemberCollector, "#{__dir__}/rng_compiler/member_collector"
|
|
21
|
+
autoload :ElementVisitor, "#{__dir__}/rng_compiler/element_visitor"
|
|
22
|
+
autoload :DefineClassifier, "#{__dir__}/rng_compiler/define_classifier"
|
|
23
|
+
autoload :ValueTypeResolver, "#{__dir__}/rng_compiler/value_type_resolver"
|
|
24
|
+
autoload :RngHelpers, "#{__dir__}/rng_compiler/rng_helpers"
|
|
25
|
+
|
|
26
|
+
# Map RNG <data type="..."/> values to Lutaml::Model attribute type
|
|
27
|
+
# symbols. Mirrors Lutaml::Xml::Schema::RelaxngSchema.get_relaxng_type
|
|
28
|
+
# so RNG-generate + compile-back is a round-trip.
|
|
29
|
+
DATA_TYPE_MAP = {
|
|
30
|
+
"string" => :string,
|
|
31
|
+
"integer" => :integer,
|
|
32
|
+
"int" => :integer,
|
|
33
|
+
"long" => :integer,
|
|
34
|
+
"boolean" => :boolean,
|
|
35
|
+
"float" => :float,
|
|
36
|
+
"double" => :float,
|
|
37
|
+
"decimal" => :decimal,
|
|
38
|
+
"date" => :date,
|
|
39
|
+
"dateTime" => :date_time,
|
|
40
|
+
"time" => :time,
|
|
41
|
+
}.freeze
|
|
42
|
+
|
|
43
|
+
DEFAULT_DATA_TYPE = :string
|
|
44
|
+
|
|
45
|
+
def to_models(rng, options = {})
|
|
46
|
+
require_rng_parser!
|
|
47
|
+
|
|
48
|
+
opts = normalize_options(options)
|
|
49
|
+
output = compile(rng, opts)
|
|
50
|
+
|
|
51
|
+
dispatch_output(output, normalize_options(opts))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def compile(rng, options = {})
|
|
55
|
+
require_rng_parser!
|
|
56
|
+
|
|
57
|
+
opts = normalize_options(options)
|
|
58
|
+
grammar = parse_grammar(rng, opts)
|
|
59
|
+
classes, namespaces = compile_grammar(grammar)
|
|
60
|
+
|
|
61
|
+
entries =
|
|
62
|
+
classes.map { |name, spec| CompiledOutput::Entry.new(name, spec, :model) } +
|
|
63
|
+
namespaces.map { |name, spec| CompiledOutput::Entry.new(name, spec, :namespace) }
|
|
64
|
+
|
|
65
|
+
CompiledOutput.new(
|
|
66
|
+
entries: entries,
|
|
67
|
+
module_namespace: opts[:module_namespace],
|
|
68
|
+
register_id: opts[:register_id],
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def require_rng_parser!
|
|
75
|
+
return if defined?(::Rng::Grammar)
|
|
76
|
+
|
|
77
|
+
raise "RNG schema compilation requires the rng gem. " \
|
|
78
|
+
"Add `gem \"rng\"` to your Gemfile."
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def normalize_options(options)
|
|
82
|
+
opts = options.dup
|
|
83
|
+
opts[:indent] ||= 2
|
|
84
|
+
opts[:register_id] ||= :default
|
|
85
|
+
opts
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def parse_grammar(rng, options)
|
|
89
|
+
return rng if rng.is_a?(::Rng::Grammar)
|
|
90
|
+
|
|
91
|
+
if options[:location]
|
|
92
|
+
::Rng.parse(rng, location: options[:location], resolve_external: true)
|
|
93
|
+
else
|
|
94
|
+
::Rng::Grammar.from_xml(rng)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def dispatch_output(output, options)
|
|
99
|
+
if options[:create_files]
|
|
100
|
+
dir = options.fetch(
|
|
101
|
+
:output_dir,
|
|
102
|
+
options.fetch(:default_output_dir, "rng_models_#{Time.now.to_i}"),
|
|
103
|
+
)
|
|
104
|
+
FileWriter.write(output, dir)
|
|
105
|
+
true
|
|
106
|
+
elsif options[:load_classes]
|
|
107
|
+
ClassLoader.load(output)
|
|
108
|
+
output.sources
|
|
109
|
+
else
|
|
110
|
+
output.sources
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Returns [classes_hash, namespaces_hash]. `classes` holds
|
|
115
|
+
# Definitions::Model / Definitions::RestrictedType / Definitions::UnionType.
|
|
116
|
+
# `namespaces` holds Definitions::Namespace.
|
|
117
|
+
def compile_grammar(grammar)
|
|
118
|
+
defines = grammar.define.to_h { |d| [d.name, d] }
|
|
119
|
+
classes = {}
|
|
120
|
+
namespaces = {}
|
|
121
|
+
registrar = ->(uri) { register_namespace(namespaces, uri) }
|
|
122
|
+
|
|
123
|
+
default_uri = grammar_default_ns(grammar)
|
|
124
|
+
visitor = ElementVisitor.new(
|
|
125
|
+
defines, classes,
|
|
126
|
+
default_namespace_class: default_uri && registrar.call(default_uri),
|
|
127
|
+
register_namespace: registrar
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
grammar.start.each do |start|
|
|
131
|
+
visitor.compile_element(start.element) if start.element
|
|
132
|
+
Array(start.ref).each do |ref|
|
|
133
|
+
target = defines[ref.name]
|
|
134
|
+
visitor.compile_define(target) if target
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Sweep: compile any <define> not reachable from <start>.
|
|
139
|
+
grammar.define.each { |define| visitor.compile_define(define) }
|
|
140
|
+
|
|
141
|
+
finalize_models(classes)
|
|
142
|
+
|
|
143
|
+
[classes, namespaces]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def grammar_default_ns(grammar)
|
|
147
|
+
uri = grammar.respond_to?(:ns) ? grammar.ns : nil
|
|
148
|
+
uri if uri.is_a?(String) && !uri.empty?
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Build-or-find the namespace for `uri`; returns its class_name.
|
|
152
|
+
# RNG/RNC name-prefixed elements are namespace-qualified, so descendant
|
|
153
|
+
# elements inherit the namespace via element_form_default :qualified.
|
|
154
|
+
def register_namespace(namespaces, uri)
|
|
155
|
+
existing = namespaces.values.find { |ns| ns.uri == uri }
|
|
156
|
+
return existing.class_name if existing
|
|
157
|
+
|
|
158
|
+
# Distinct URIs can map to the same generated class name AND prefix
|
|
159
|
+
# (e.g. both `urn:a` and `urn:b` -> "DefaultNamespace"/"ns"); uniquify
|
|
160
|
+
# both so one namespace does not silently overwrite the other or
|
|
161
|
+
# collide with its prefix at serialization.
|
|
162
|
+
class_name = RngHelpers.unique_class_name(
|
|
163
|
+
namespaces, NamespaceNaming.class_name_for(uri)
|
|
164
|
+
)
|
|
165
|
+
ns = Definitions::Namespace.new(
|
|
166
|
+
class_name: class_name,
|
|
167
|
+
uri: uri,
|
|
168
|
+
prefix_default: unique_prefix(namespaces, NamespaceNaming.prefix_for(uri)),
|
|
169
|
+
element_form_default: :qualified,
|
|
170
|
+
)
|
|
171
|
+
namespaces[class_name] = ns
|
|
172
|
+
class_name
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# A prefix that does not collide with an already-registered namespace's
|
|
176
|
+
# prefix (appends 2, 3, ... on collision).
|
|
177
|
+
def unique_prefix(namespaces, base)
|
|
178
|
+
taken = namespaces.values.map(&:prefix_default)
|
|
179
|
+
return base unless taken.include?(base)
|
|
180
|
+
|
|
181
|
+
counter = 2
|
|
182
|
+
counter += 1 while taken.include?("#{base}#{counter}")
|
|
183
|
+
"#{base}#{counter}"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Set `required_files` so each generated file requires its
|
|
187
|
+
# dependencies: a Model requires its imports/class_ref types/namespace;
|
|
188
|
+
# a namespaced RestrictedType requires its namespace class.
|
|
189
|
+
def finalize_models(classes)
|
|
190
|
+
classes.each_value do |spec|
|
|
191
|
+
case spec
|
|
192
|
+
when Definitions::Model
|
|
193
|
+
spec.required_files =
|
|
194
|
+
Renderers::RequiredFilesCalculator.class_names_for_rng(spec)
|
|
195
|
+
.map { |dep| require_relative_line(dep) }
|
|
196
|
+
when Definitions::RestrictedType
|
|
197
|
+
next unless spec.namespace_class_name
|
|
198
|
+
|
|
199
|
+
spec.required_files = namespaced_type_requires(spec)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# A namespaced RestrictedType requires its namespace class and, when it
|
|
205
|
+
# subclasses another generated type (a bare name, not a Lutaml built-in),
|
|
206
|
+
# that parent too.
|
|
207
|
+
def namespaced_type_requires(spec)
|
|
208
|
+
deps = [spec.namespace_class_name]
|
|
209
|
+
parent = spec.parent_class
|
|
210
|
+
deps << parent if parent && !parent.include?("::")
|
|
211
|
+
deps.map { |dep| require_relative_line(dep) }
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def require_relative_line(class_name)
|
|
215
|
+
%(require_relative "#{Utils.snake_case(class_name)}")
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module Model
|
|
7
|
+
module Schema
|
|
8
|
+
# Templates that are *actually* shared between schema compilers
|
|
9
|
+
# (XSD, RNG, future formats). Single-consumer templates live in
|
|
10
|
+
# the renderer that uses them — putting them here would just be
|
|
11
|
+
# relocation, not deduplication.
|
|
12
|
+
#
|
|
13
|
+
# Each constant is an `ERB.new(...)` evaluated via
|
|
14
|
+
# `TEMPLATE.result(binding)` from a renderer that exposes the
|
|
15
|
+
# required hook methods (documented above each template).
|
|
16
|
+
module Templates
|
|
17
|
+
# ----------------------------------------------------------------
|
|
18
|
+
# Single shape for any Lutaml::Model::Serializable subclass
|
|
19
|
+
# generated by ANY schema compiler. There is no per-format
|
|
20
|
+
# variant — adding a new xml directive here propagates to every
|
|
21
|
+
# compiler that uses this template. Consumed by:
|
|
22
|
+
# - Lutaml::Model::Schema::Renderers::Model
|
|
23
|
+
# (renders XSD complex types / groups and RNG generated classes)
|
|
24
|
+
#
|
|
25
|
+
# Required binding methods (renderer must expose all of these,
|
|
26
|
+
# returning "" / nil / false for inapplicable sections):
|
|
27
|
+
#
|
|
28
|
+
# from ClassBoilerplate:
|
|
29
|
+
# module_opening, module_closing,
|
|
30
|
+
# registration_methods, registration_execution,
|
|
31
|
+
# boilerplate_indent_str
|
|
32
|
+
#
|
|
33
|
+
# identity / parent:
|
|
34
|
+
# rendered_class_name String — CamelCase
|
|
35
|
+
# serializable_class_parent String — e.g. "Lutaml::Model::Serializable"
|
|
36
|
+
#
|
|
37
|
+
# header:
|
|
38
|
+
# serializable_class_required_files String — `require_relative` lines or ""
|
|
39
|
+
# serializable_class_documentation String — doc comment block or ""
|
|
40
|
+
#
|
|
41
|
+
# body — attribute declarations:
|
|
42
|
+
# serializable_class_attributes String — `attribute :foo, :bar` lines
|
|
43
|
+
# serializable_class_imports String — `import_model :foo` lines or ""
|
|
44
|
+
#
|
|
45
|
+
# body — xml block:
|
|
46
|
+
# xml_root_directive_line String or nil — `element "X"` /
|
|
47
|
+
# `type_name "X"` / nil to skip
|
|
48
|
+
# xml_namespace_line String or nil — `namespace ClassName` / nil
|
|
49
|
+
# xml_mixed_content? Boolean — emit `mixed_content`
|
|
50
|
+
# xml_text_content? Boolean — emit `map_content to: :content`
|
|
51
|
+
# xml_attribute_mappings String — `map_element`/`map_attribute` lines
|
|
52
|
+
# xml_extra_mappings String — extra mapping lines or ""
|
|
53
|
+
# (e.g. XSD's simple_content tail)
|
|
54
|
+
# ----------------------------------------------------------------
|
|
55
|
+
SERIALIZABLE_CLASS = ERB.new(<<~TMPL, trim_mode: "-")
|
|
56
|
+
# frozen_string_literal: true
|
|
57
|
+
|
|
58
|
+
require "lutaml/model"
|
|
59
|
+
<%= serializable_class_required_files -%>
|
|
60
|
+
<%= module_opening -%>
|
|
61
|
+
<%= serializable_class_documentation -%>
|
|
62
|
+
class <%= rendered_class_name %> < <%= serializable_class_parent %>
|
|
63
|
+
<%= serializable_class_attributes -%>
|
|
64
|
+
<%= serializable_class_imports -%>
|
|
65
|
+
<%= "\n" if serializable_class_attributes.length.positive? || serializable_class_imports.length.positive? -%>
|
|
66
|
+
<%= boilerplate_indent_str %>xml do
|
|
67
|
+
<%- if xml_root_directive_line -%>
|
|
68
|
+
<%= boilerplate_indent_str * 2 %><%= xml_root_directive_line %>
|
|
69
|
+
<%- end -%>
|
|
70
|
+
<%- if xml_namespace_line -%>
|
|
71
|
+
<%= boilerplate_indent_str * 2 %><%= xml_namespace_line %>
|
|
72
|
+
<%- end -%>
|
|
73
|
+
<%- if xml_mixed_content? -%>
|
|
74
|
+
<%= boilerplate_indent_str * 2 %>mixed_content
|
|
75
|
+
<%- end -%>
|
|
76
|
+
<%- if xml_text_content? -%>
|
|
77
|
+
<%= boilerplate_indent_str * 2 %>map_content to: :content
|
|
78
|
+
<%- end -%>
|
|
79
|
+
<%= xml_attribute_mappings -%>
|
|
80
|
+
<%= xml_extra_mappings -%>
|
|
81
|
+
<%= boilerplate_indent_str %>end
|
|
82
|
+
<%= registration_methods -%>
|
|
83
|
+
end
|
|
84
|
+
<%= module_closing -%>
|
|
85
|
+
<%= registration_execution -%>
|
|
86
|
+
TMPL
|
|
87
|
+
|
|
88
|
+
# XmlNamespace subclass. Consumed by:
|
|
89
|
+
# - Lutaml::Model::Schema::Renderers::Namespace
|
|
90
|
+
# Binding: module_opening, module_closing, class_name, uri, prefix_default.
|
|
91
|
+
XML_NAMESPACE = ERB.new(<<~TMPL, trim_mode: "-")
|
|
92
|
+
# frozen_string_literal: true
|
|
93
|
+
|
|
94
|
+
require "lutaml/model"
|
|
95
|
+
|
|
96
|
+
<%= module_opening -%>
|
|
97
|
+
# Namespace: <%= uri %>
|
|
98
|
+
class <%= class_name %> < Lutaml::Xml::W3c::XmlNamespace
|
|
99
|
+
uri <%= uri.inspect %>
|
|
100
|
+
prefix_default <%= prefix_default.inspect %>
|
|
101
|
+
<%- if element_form_default_line -%>
|
|
102
|
+
<%= element_form_default_line %>
|
|
103
|
+
<%- end -%>
|
|
104
|
+
end
|
|
105
|
+
<%= module_closing -%>
|
|
106
|
+
TMPL
|
|
107
|
+
|
|
108
|
+
# Restricted simple-type subclass (cast method that mutates options
|
|
109
|
+
# with facets then delegates to super). Consumed by:
|
|
110
|
+
# - Lutaml::Model::Schema::Renderers::RestrictedType
|
|
111
|
+
# Binding: module_opening, module_closing, registration_methods,
|
|
112
|
+
# registration_execution, rendered_class_name, parent_class,
|
|
113
|
+
# xml_namespace_line, restricted_simple_type_required_files,
|
|
114
|
+
# restricted_simple_type_cast_body, boilerplate_indent_str.
|
|
115
|
+
RESTRICTED_SIMPLE_TYPE = ERB.new(<<~TMPL, trim_mode: "-")
|
|
116
|
+
# frozen_string_literal: true
|
|
117
|
+
require "lutaml/model"
|
|
118
|
+
<%= restricted_simple_type_required_files -%>
|
|
119
|
+
<%= module_opening -%>
|
|
120
|
+
class <%= rendered_class_name %><%= " < \#{parent_class}" if parent_class %>
|
|
121
|
+
<%- if xml_namespace_line -%>
|
|
122
|
+
<%= boilerplate_indent_str %>xml do
|
|
123
|
+
<%= boilerplate_indent_str * 2 %><%= xml_namespace_line %>
|
|
124
|
+
<%= boilerplate_indent_str %>end
|
|
125
|
+
<%- end -%>
|
|
126
|
+
<%= boilerplate_indent_str %>def self.cast(value, options = {})
|
|
127
|
+
<%= boilerplate_indent_str * 2 %>return if value.nil?
|
|
128
|
+
|
|
129
|
+
<%= restricted_simple_type_cast_body -%>
|
|
130
|
+
<%= boilerplate_indent_str * 2 %>value = super(value, options)
|
|
131
|
+
<%= boilerplate_indent_str * 2 %>value
|
|
132
|
+
<%= boilerplate_indent_str %>end
|
|
133
|
+
<%= registration_methods -%>
|
|
134
|
+
end
|
|
135
|
+
<%= module_closing -%>
|
|
136
|
+
<%= registration_execution -%>
|
|
137
|
+
TMPL
|
|
138
|
+
|
|
139
|
+
# Lutaml::Model::Type::Value union subclass. Consumed by:
|
|
140
|
+
# - Lutaml::Model::Schema::Renderers::Union
|
|
141
|
+
# `union_cast_body` selects the cast strategy from the spec
|
|
142
|
+
# (XSD: `||`-chained resolve_type; RNG: each+rescue).
|
|
143
|
+
UNION_TYPE = ERB.new(<<~TMPL, trim_mode: "-")
|
|
144
|
+
# frozen_string_literal: true
|
|
145
|
+
require "lutaml/model"
|
|
146
|
+
<%= union_required_files -%>
|
|
147
|
+
<%= module_opening -%>
|
|
148
|
+
class <%= rendered_class_name %> < Lutaml::Model::Type::Value
|
|
149
|
+
<%= boilerplate_indent_str %>def self.cast(value, options = {})
|
|
150
|
+
<%= boilerplate_indent_str * 2 %>return if value.nil?
|
|
151
|
+
|
|
152
|
+
<%= union_cast_body -%>
|
|
153
|
+
<%= boilerplate_indent_str %>end
|
|
154
|
+
<%= registration_methods -%>
|
|
155
|
+
end
|
|
156
|
+
<%= module_closing -%>
|
|
157
|
+
<%= registration_execution -%>
|
|
158
|
+
TMPL
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|