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,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
|
|
@@ -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
|