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,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
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module XmlCompiler
|
|
7
|
+
# Iteration helper for the Lutaml::Xml XSD AST. Wraps the
|
|
8
|
+
# `#element_order` array each AST node carries with the special
|
|
9
|
+
# cases the compiler relies on:
|
|
10
|
+
# * built-in `Xsd::Base` nodes already produce a resolved order
|
|
11
|
+
# * `text`-only entries and `<import>` / `<include>` siblings
|
|
12
|
+
# are skipped
|
|
13
|
+
# * placeholders in the order are mapped to the matching child
|
|
14
|
+
# accessor on the node so callers receive concrete AST values.
|
|
15
|
+
module ElementOrder
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
def resolved(object)
|
|
19
|
+
return [] if object.element_order.nil?
|
|
20
|
+
|
|
21
|
+
if object.is_a?(Lutaml::Xml::Schema::Xsd::Base)
|
|
22
|
+
return object.resolved_element_order
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
object.element_order.each_with_object(object.element_order.dup) do |builder, array|
|
|
26
|
+
next array.delete(builder) if builder.text? || ELEMENT_ORDER_IGNORABLE.include?(builder.name)
|
|
27
|
+
|
|
28
|
+
index = 0
|
|
29
|
+
array.each_with_index do |element, i|
|
|
30
|
+
next unless element == builder
|
|
31
|
+
|
|
32
|
+
array[i] = Array(object.public_send(Utils.snake_case(builder.name)))[index]
|
|
33
|
+
index += 1
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -1,124 +1,48 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "erb"
|
|
4
|
-
|
|
5
3
|
module Lutaml
|
|
6
4
|
module Model
|
|
7
5
|
module Schema
|
|
8
6
|
module XmlCompiler
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<%= module_opening %>
|
|
15
|
-
<%= autoload_declarations %>
|
|
16
|
-
|
|
17
|
-
def self.register_all
|
|
18
|
-
# Ensure context exists before using it
|
|
19
|
-
context = Lutaml::Model::GlobalContext.context(:<%= @register_id %>) ||
|
|
20
|
-
Lutaml::Model::GlobalContext.create_context(id: :<%= @register_id %>)
|
|
21
|
-
|
|
22
|
-
# Phase 1: Register all models (no imports)
|
|
23
|
-
<%= registration_calls %>
|
|
24
|
-
|
|
25
|
-
# Phase 2: Resolve model, choice, and restrict imports
|
|
26
|
-
<%= model_import_resolution_calls %>
|
|
27
|
-
|
|
28
|
-
# Phase 3: Resolve mapping and sequence imports
|
|
29
|
-
<%= mapping_import_resolution_calls %>
|
|
30
|
-
end
|
|
31
|
-
<%= module_closing %>
|
|
32
|
-
TEMPLATE
|
|
33
|
-
|
|
34
|
-
def self.generate(classes_hash, options = {})
|
|
35
|
-
new(classes_hash, options).generate
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def initialize(classes_hash, options)
|
|
39
|
-
@classes = classes_hash
|
|
40
|
-
@module_namespace = options[:module_namespace]
|
|
41
|
-
@register_id = options[:register_id] || :default
|
|
42
|
-
# Cache split modules for reuse
|
|
43
|
-
@modules = @module_namespace&.split("::") || []
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def generate
|
|
47
|
-
return nil unless @module_namespace
|
|
48
|
-
|
|
49
|
-
TEMPLATE.result(binding)
|
|
50
|
-
end
|
|
51
|
-
|
|
7
|
+
# XSD registry generator. Extends the shared base with an extra
|
|
8
|
+
# phase for resolving model imports — XSD-generated classes use
|
|
9
|
+
# ensure_imports! hooks that need to run after every model is
|
|
10
|
+
# registered.
|
|
11
|
+
class RegistryGenerator < Lutaml::Model::Schema::RegistryGenerator
|
|
52
12
|
private
|
|
53
13
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"#{' ' * (@modules.size - i - 1)}end"
|
|
67
|
-
end.join("\n")
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def autoload_declarations
|
|
71
|
-
indent = " " * module_depth
|
|
72
|
-
@classes.keys.filter_map do |name|
|
|
73
|
-
class_name = Utils.camel_case(name)
|
|
74
|
-
file_name = Utils.snake_case(name)
|
|
75
|
-
# Use relative path from registry file location
|
|
76
|
-
module_subdir = @module_namespace.split("::").last.downcase
|
|
77
|
-
"#{indent}autoload :#{class_name}, File.join(__dir__, \"#{module_subdir}\", \"#{file_name}\")"
|
|
78
|
-
end.join("\n")
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def registration_calls
|
|
82
|
-
indent = " " * (module_depth + 1)
|
|
83
|
-
@classes.keys.filter_map do |name|
|
|
84
|
-
# Skip namespace classes (check the name, not the content)
|
|
85
|
-
next if name.to_s.include?("Namespace")
|
|
86
|
-
|
|
87
|
-
class_name = Utils.camel_case(name)
|
|
88
|
-
id = Utils.snake_case(name).to_sym
|
|
89
|
-
"#{indent}#{class_name} # Trigger autoload\n#{indent}context.registry.register(:#{id}, #{class_name})\n#{indent}#{class_name}.instance_variable_set(:@register, :#{@register_id})"
|
|
90
|
-
end.join("\n")
|
|
14
|
+
# Override the shared `registration_body` hook with three phases.
|
|
15
|
+
def registration_body
|
|
16
|
+
[
|
|
17
|
+
"#{body_indent}# Phase 1: Register all models (no imports)",
|
|
18
|
+
registration_calls,
|
|
19
|
+
"",
|
|
20
|
+
"#{body_indent}# Phase 2: Resolve model, choice, and restrict imports",
|
|
21
|
+
model_import_resolution_calls,
|
|
22
|
+
"",
|
|
23
|
+
"#{body_indent}# Phase 3: Resolve serialization mapping imports (sequence imports)",
|
|
24
|
+
mapping_import_resolution_calls,
|
|
25
|
+
].join("\n")
|
|
91
26
|
end
|
|
92
27
|
|
|
93
28
|
def model_import_resolution_calls
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
@
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
class_name = Utils.camel_case(name)
|
|
100
|
-
"#{indent}#{class_name}.ensure_imports!(:#{@register_id}) if #{class_name}.respond_to?(:ensure_imports!)"
|
|
101
|
-
end.join("\n")
|
|
29
|
+
@classes.map do |entry|
|
|
30
|
+
class_name = Utils.camel_case(entry.name)
|
|
31
|
+
"#{body_indent}#{class_name}.ensure_imports!(:#{@register_id}) " \
|
|
32
|
+
"if #{class_name}.respond_to?(:ensure_imports!)"
|
|
33
|
+
end.join("\n")
|
|
102
34
|
end
|
|
103
35
|
|
|
36
|
+
# Only Serializable subclasses have mappings; simple-type and
|
|
37
|
+
# union-type subclasses (Lutaml::Model::Type::Value) do not.
|
|
38
|
+
# The emitted code guards with respond_to? so it works
|
|
39
|
+
# uniformly across both kinds of generated class.
|
|
104
40
|
def mapping_import_resolution_calls
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
class_name = Utils.camel_case(name)
|
|
112
|
-
"#{indent}#{class_name}.mappings[:xml].ensure_mappings_imported!(:#{@register_id}) if #{class_name}.mappings[:xml]&.respond_to?(:ensure_mappings_imported!)"
|
|
113
|
-
end.join("\n")
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def module_depth
|
|
117
|
-
@modules.size
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def module_path
|
|
121
|
-
@modules.map(&:downcase).join("/")
|
|
41
|
+
@classes.map do |entry|
|
|
42
|
+
class_name = Utils.camel_case(entry.name)
|
|
43
|
+
"#{body_indent}#{class_name}.mappings[:xml].ensure_mappings_imported!(:#{@register_id}) " \
|
|
44
|
+
"if #{class_name}.respond_to?(:mappings) && #{class_name}.mappings[:xml]&.respond_to?(:ensure_mappings_imported!)"
|
|
45
|
+
end.join("\n")
|
|
122
46
|
end
|
|
123
47
|
end
|
|
124
48
|
end
|