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,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Renderers
|
|
7
|
+
# Renders the `map_element` / `map_attribute` / `sequence do` lines
|
|
8
|
+
# inside the `xml do ... end` block of a generated class. Used by
|
|
9
|
+
# Renderers::Model.
|
|
10
|
+
class Mappings
|
|
11
|
+
def self.render(members, indent:, base_indent:, simple_content: nil)
|
|
12
|
+
new(indent: indent, base_indent: base_indent,
|
|
13
|
+
simple_content: simple_content).render(members)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(indent:, base_indent:, simple_content:)
|
|
17
|
+
@indent = indent
|
|
18
|
+
@base_indent = base_indent
|
|
19
|
+
@simple_content = simple_content
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def render(members)
|
|
23
|
+
members.map { |m| render_one(m, @indent) }.join +
|
|
24
|
+
simple_content_attribute_mappings
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def render_one(member, indent)
|
|
30
|
+
case member
|
|
31
|
+
when Definitions::Sequence then render_sequence(member, indent)
|
|
32
|
+
when Definitions::Choice then member.alternatives.map { |a| render_one(a, indent) }.join
|
|
33
|
+
when Definitions::Attribute then render_attribute(member, indent)
|
|
34
|
+
when Definitions::GroupImport then "#{indent}import_model_mappings :#{member.name}\n"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def render_sequence(sequence, indent)
|
|
39
|
+
inner_indent = indent + @base_indent
|
|
40
|
+
inner = sequence.members.map { |m| render_one(m, inner_indent) }.join
|
|
41
|
+
"#{indent}sequence do\n#{inner}#{indent}end\n"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def render_attribute(attr, indent)
|
|
45
|
+
case attr.kind
|
|
46
|
+
when :element then map_member("map_element", attr, indent)
|
|
47
|
+
when :attribute then map_member("map_attribute", attr, indent)
|
|
48
|
+
else ""
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def map_member(verb, attr, indent)
|
|
53
|
+
%(#{indent}#{verb} "#{attr.xml_name}", to: :#{attr.name}#{render_options(attr)}\n)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def render_options(attr)
|
|
57
|
+
opts = []
|
|
58
|
+
opts << "render_default: true" if attr.render_default
|
|
59
|
+
opts << "render_empty: true" if attr.render_empty
|
|
60
|
+
opts.empty? ? "" : ", #{opts.join(', ')}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def simple_content_attribute_mappings
|
|
64
|
+
return "" unless @simple_content
|
|
65
|
+
|
|
66
|
+
@simple_content.additional_attributes.map { |a| render_one(a, @indent) }.join
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Renderers
|
|
7
|
+
# Renders the attribute-declaration block of a generated class:
|
|
8
|
+
# the lines between `class X < Parent` and `xml do`. Used by
|
|
9
|
+
# Renderers::Model.
|
|
10
|
+
class MemberDecls
|
|
11
|
+
def self.render(members, indent:, base_indent:, mixed: false,
|
|
12
|
+
text_content: false, simple_content: nil)
|
|
13
|
+
new(indent: indent, base_indent: base_indent,
|
|
14
|
+
mixed: mixed,
|
|
15
|
+
text_content: text_content,
|
|
16
|
+
simple_content: simple_content).render(members)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize(indent:, base_indent:, mixed:, text_content:,
|
|
20
|
+
simple_content:)
|
|
21
|
+
@indent = indent
|
|
22
|
+
@base_indent = base_indent
|
|
23
|
+
@mixed = mixed
|
|
24
|
+
@text_content = text_content
|
|
25
|
+
@simple_content = simple_content
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def render(members)
|
|
29
|
+
lines = members.map { |m| render_one(m, @indent) }.join
|
|
30
|
+
lines + content_attribute_line + simple_content_extras
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def render_one(member, indent)
|
|
36
|
+
case member
|
|
37
|
+
when Definitions::Choice then render_choice(member, indent)
|
|
38
|
+
when Definitions::Sequence then member.members.map { |m| render_one(m, indent) }.join
|
|
39
|
+
when Definitions::Attribute then render_attribute(member, indent)
|
|
40
|
+
when Definitions::GroupImport then "#{indent}import_model_attributes :#{member.name}\n"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def render_choice(choice, indent)
|
|
45
|
+
inner_indent = indent + @base_indent
|
|
46
|
+
inner = choice.alternatives.map { |alt| render_one(alt, inner_indent) }.join
|
|
47
|
+
"#{indent}#{choice.header} do\n#{inner}#{indent}end\n"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def render_attribute(attr, indent)
|
|
51
|
+
doc = format_doc(attr.documentation, indent)
|
|
52
|
+
"#{doc}#{indent}attribute :#{attr.name}, #{type_literal(attr.type)}#{options_suffix(attr)}\n"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def format_doc(doc, indent)
|
|
56
|
+
return "" if Utils.blank?(doc&.to_s&.strip)
|
|
57
|
+
|
|
58
|
+
doc.to_s.lines.map { |line| "#{indent}# #{line.strip}\n" }.join
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def type_literal(type_ref)
|
|
62
|
+
case type_ref.kind
|
|
63
|
+
when :symbol then ":#{type_ref.value}"
|
|
64
|
+
when :class_ref then type_ref.value
|
|
65
|
+
when :w3c then "::#{type_ref.value}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def options_suffix(attr)
|
|
70
|
+
opts = []
|
|
71
|
+
opts << collection_option(attr.collection) if attr.collection
|
|
72
|
+
opts << "default: -> { #{attr.default.inspect} }" if attr.default
|
|
73
|
+
opts << "initialize_empty: true" if attr.initialize_empty
|
|
74
|
+
opts.empty? ? "" : ", #{opts.join(', ')}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def collection_option(coll)
|
|
78
|
+
case coll
|
|
79
|
+
when true then "collection: true"
|
|
80
|
+
when Range then range_option(coll)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def range_option(range)
|
|
85
|
+
ending = if range.end.respond_to?(:infinite?) && range.end.infinite?
|
|
86
|
+
"Float::INFINITY"
|
|
87
|
+
else
|
|
88
|
+
range.end
|
|
89
|
+
end
|
|
90
|
+
"collection: #{range.begin}..#{ending}"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def content_attribute_line
|
|
94
|
+
return simple_content_attribute if @simple_content
|
|
95
|
+
return "#{@indent}attribute :content, :string, collection: true\n" if @mixed
|
|
96
|
+
return "#{@indent}attribute :content, :string\n" if @text_content
|
|
97
|
+
|
|
98
|
+
""
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def simple_content_attribute
|
|
102
|
+
type_sym = Utils.snake_case(Utils.last_of_split(@simple_content.base_class))
|
|
103
|
+
"#{@indent}attribute :content, :#{type_sym}\n"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def simple_content_extras
|
|
107
|
+
return "" unless @simple_content
|
|
108
|
+
|
|
109
|
+
@simple_content.additional_attributes.map { |a| render_attribute(a, @indent) }.join
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module Model
|
|
7
|
+
module Schema
|
|
8
|
+
module Renderers
|
|
9
|
+
# Renders a Definitions::Model into a Lutaml::Model::Serializable
|
|
10
|
+
# subclass as Ruby source. Member-declaration and xml-mapping
|
|
11
|
+
# rendering is delegated to MemberDecls and Mappings.
|
|
12
|
+
class Model
|
|
13
|
+
def self.render(spec, **)
|
|
14
|
+
new(spec, **).render
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize(spec, indent: 2, module_namespace: nil, register_id: :default)
|
|
18
|
+
@spec = spec
|
|
19
|
+
@indent = indent.is_a?(Integer) ? " " * indent : indent
|
|
20
|
+
@extended_indent = @indent * 2
|
|
21
|
+
@raw_module_namespace = module_namespace
|
|
22
|
+
@register_id = register_id
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def render
|
|
26
|
+
Templates::SERIALIZABLE_CLASS.result(binding)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# Effective namespace (nil when spec opts out of module wrapping).
|
|
32
|
+
def module_namespace
|
|
33
|
+
@spec.module_wrappable ? @raw_module_namespace : nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def modules
|
|
37
|
+
module_namespace&.split("::") || []
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def rendered_class_name = @spec.class_name
|
|
41
|
+
def serializable_class_parent = @spec.parent_class
|
|
42
|
+
def serializable_class_required_files = format_files(@spec.required_files)
|
|
43
|
+
def serializable_class_documentation = format_doc_block(@spec.documentation)
|
|
44
|
+
def serializable_class_imports = render_imports
|
|
45
|
+
|
|
46
|
+
def serializable_class_attributes
|
|
47
|
+
MemberDecls.render(
|
|
48
|
+
@spec.members,
|
|
49
|
+
indent: @indent,
|
|
50
|
+
base_indent: @indent,
|
|
51
|
+
mixed: @spec.mixed,
|
|
52
|
+
text_content: @spec.text_content,
|
|
53
|
+
simple_content: @spec.simple_content,
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def xml_attribute_mappings
|
|
58
|
+
Mappings.render(
|
|
59
|
+
@spec.members,
|
|
60
|
+
indent: @extended_indent,
|
|
61
|
+
base_indent: @indent,
|
|
62
|
+
simple_content: @spec.simple_content,
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def xml_root_directive_line
|
|
67
|
+
case @spec.xml_root.kind
|
|
68
|
+
when :element then %(element "#{@spec.xml_root.name}")
|
|
69
|
+
when :type_name then %(type_name "#{@spec.xml_root.name}")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def xml_namespace_line
|
|
74
|
+
ns = @spec.namespace_class_name
|
|
75
|
+
ns && "namespace #{ns}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def xml_mixed_content? = @spec.mixed
|
|
79
|
+
def xml_text_content? = @spec.text_content || !!@spec.simple_content || @spec.mixed
|
|
80
|
+
def xml_extra_mappings = ""
|
|
81
|
+
|
|
82
|
+
def module_opening = ModuleNesting.opening(modules)
|
|
83
|
+
def module_closing = ModuleNesting.closing(modules)
|
|
84
|
+
def boilerplate_indent_str = @indent
|
|
85
|
+
|
|
86
|
+
def registration_methods
|
|
87
|
+
Registration.methods_block(
|
|
88
|
+
class_name: @spec.class_name,
|
|
89
|
+
module_namespace: module_namespace,
|
|
90
|
+
indent: @indent,
|
|
91
|
+
lazy: @spec.lazy_register,
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def registration_execution
|
|
96
|
+
Registration.execution_line(
|
|
97
|
+
class_name: @spec.class_name,
|
|
98
|
+
module_namespace: module_namespace,
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def format_files(files)
|
|
103
|
+
return "" if files.empty? || module_namespace
|
|
104
|
+
|
|
105
|
+
"#{files.uniq.join("\n")}\n"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def format_doc_block(doc)
|
|
109
|
+
return "" if Utils.blank?(doc&.to_s&.strip)
|
|
110
|
+
|
|
111
|
+
doc.to_s.lines.map { |line| "# #{line.strip}\n" }.join
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def render_imports
|
|
115
|
+
@spec.imports.map { |name| "#{@indent}import_model #{name}\n" }.join
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module Model
|
|
7
|
+
module Schema
|
|
8
|
+
module Renderers
|
|
9
|
+
# Renders a Definitions::Namespace into a
|
|
10
|
+
# Lutaml::Xml::W3c::XmlNamespace subclass.
|
|
11
|
+
class Namespace < Base
|
|
12
|
+
def render
|
|
13
|
+
Templates::XML_NAMESPACE.result(binding)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def class_name = @spec.class_name
|
|
19
|
+
def uri = @spec.uri
|
|
20
|
+
def prefix_default = @spec.prefix_default
|
|
21
|
+
|
|
22
|
+
def element_form_default_line
|
|
23
|
+
efd = @spec.element_form_default
|
|
24
|
+
efd && "element_form_default #{efd.inspect}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Renderers
|
|
7
|
+
# Stateless helper for emitting the `self.register` and
|
|
8
|
+
# `self.register_class_with_id` method block plus the trailing
|
|
9
|
+
# execution line, shared by the Model, RestrictedType, and Union
|
|
10
|
+
# renderers.
|
|
11
|
+
#
|
|
12
|
+
# Cases:
|
|
13
|
+
# module_namespace = nil -> emit both methods + execution
|
|
14
|
+
# namespace + keep_when_namespaced -> emit only `self.register`
|
|
15
|
+
# namespace + !keep -> emit nothing
|
|
16
|
+
module Registration
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
def methods_block(class_name:, module_namespace:, indent:,
|
|
20
|
+
lazy: false, keep_when_namespaced: false)
|
|
21
|
+
return "" if module_namespace && !keep_when_namespaced
|
|
22
|
+
return register_only_block(indent, lazy) if module_namespace
|
|
23
|
+
|
|
24
|
+
full_block(class_name, indent, lazy)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def execution_line(class_name:, module_namespace:)
|
|
28
|
+
return "" if module_namespace
|
|
29
|
+
|
|
30
|
+
"\n#{class_name}.register_class_with_id\n"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def register_only_block(indent, lazy)
|
|
34
|
+
<<~REG.gsub(/^/, indent)
|
|
35
|
+
|
|
36
|
+
def self.register
|
|
37
|
+
#{indent}#{register_body(lazy)}
|
|
38
|
+
end
|
|
39
|
+
REG
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def full_block(class_name, indent, lazy)
|
|
43
|
+
target = Utils.snake_case(class_name).to_sym
|
|
44
|
+
<<~REG.gsub(/^/, indent)
|
|
45
|
+
|
|
46
|
+
def self.register
|
|
47
|
+
#{indent}#{register_body(lazy)}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.register_class_with_id
|
|
51
|
+
#{indent}context = Lutaml::Model::GlobalContext.context(Lutaml::Model::Config.default_register)
|
|
52
|
+
#{indent}context.registry.register(:#{target}, self)
|
|
53
|
+
end
|
|
54
|
+
REG
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def register_body(lazy)
|
|
58
|
+
lazy ? "@register ||= Lutaml::Model::Config.default_register" : "Lutaml::Model::Config.default_register"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Renderers
|
|
7
|
+
# Computes the `require_relative` / `require` lines that a
|
|
8
|
+
# generated Definitions::Model needs at the top of its file.
|
|
9
|
+
#
|
|
10
|
+
# XSD and RNG have different strategies because their type
|
|
11
|
+
# references mean different things:
|
|
12
|
+
#
|
|
13
|
+
# - XSD attributes carry TypeRef(:w3c | :symbol | :class_ref):
|
|
14
|
+
# :w3c -> require "lutaml/xml/w3c"
|
|
15
|
+
# :symbol -> require_relative "<value>"
|
|
16
|
+
# (unless the value is a skippable built-in)
|
|
17
|
+
# decimal is special: require "bigdecimal"
|
|
18
|
+
# :class_ref -> no require (class is autoloaded by registry)
|
|
19
|
+
# Plus any required_files surfaced by simple_content + the
|
|
20
|
+
# namespace class.
|
|
21
|
+
#
|
|
22
|
+
# - RNG attributes only ever reference other generated classes
|
|
23
|
+
# via :class_ref, so the calculator only collects class names
|
|
24
|
+
# (imports + class_ref types + namespace) and the caller
|
|
25
|
+
# formats them as `require_relative "snake_case_name"`.
|
|
26
|
+
module RequiredFilesCalculator
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
# XSD strategy. `skippable_type?` is a one-arg predicate the
|
|
30
|
+
# caller supplies so the calculator doesn't have to know the
|
|
31
|
+
# XSD built-in type table.
|
|
32
|
+
def for_xml(model, skippable_type:)
|
|
33
|
+
deps = []
|
|
34
|
+
Definitions::MemberWalk.each_attribute(model.members) do |attr|
|
|
35
|
+
deps.concat(xml_attribute_requires(attr, skippable_type))
|
|
36
|
+
end
|
|
37
|
+
deps.concat(simple_content_requires(model.simple_content, skippable_type))
|
|
38
|
+
deps << namespace_require(model.namespace_class_name) if model.namespace_class_name
|
|
39
|
+
deps.uniq
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# RNG strategy. Returns the de-duplicated list of generated-class
|
|
43
|
+
# names the model depends on; caller wraps as `require_relative`.
|
|
44
|
+
def class_names_for_rng(model)
|
|
45
|
+
deps = model.imports.dup
|
|
46
|
+
Definitions::MemberWalk.each_attribute(model.members) do |attr|
|
|
47
|
+
deps << attr.type.value if attr.type.kind == :class_ref
|
|
48
|
+
end
|
|
49
|
+
deps << model.namespace_class_name if model.namespace_class_name
|
|
50
|
+
deps.uniq
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def namespace_require(class_name)
|
|
54
|
+
%(require_relative "#{Utils.snake_case(class_name)}")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def xml_attribute_requires(attr, skippable_type)
|
|
58
|
+
ref = attr.type
|
|
59
|
+
return [] unless ref
|
|
60
|
+
|
|
61
|
+
case ref.kind
|
|
62
|
+
when :w3c then ['require "lutaml/xml/w3c"']
|
|
63
|
+
when :symbol then xml_symbol_requires(ref.value, skippable_type)
|
|
64
|
+
when :class_ref then []
|
|
65
|
+
else []
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def xml_symbol_requires(value, skippable_type)
|
|
70
|
+
return [%(require "bigdecimal")] if value == "decimal"
|
|
71
|
+
return [] if skippable_type.call(value)
|
|
72
|
+
|
|
73
|
+
[%(require_relative "#{value}")]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# base_class arrives as a raw XSD name (e.g. "xs:dateTime"). The
|
|
77
|
+
# built-in skippable check keys on the XSD spelling (`:dateTime`),
|
|
78
|
+
# not the snake_case file form, so test against `local` directly
|
|
79
|
+
# before emitting the require_relative.
|
|
80
|
+
def simple_content_requires(simple_content, skippable_type)
|
|
81
|
+
return [] if simple_content.nil? || simple_content.base_class.nil?
|
|
82
|
+
|
|
83
|
+
local = Utils.last_of_split(simple_content.base_class)
|
|
84
|
+
return [%(require "bigdecimal")] if local == "decimal"
|
|
85
|
+
return [] if skippable_type.call(local)
|
|
86
|
+
|
|
87
|
+
[%(require_relative "#{Utils.snake_case(local)}")]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module Model
|
|
7
|
+
module Schema
|
|
8
|
+
module Renderers
|
|
9
|
+
# Renders a Definitions::RestrictedType into a Ruby class extending
|
|
10
|
+
# a Lutaml::Model::Type::* with a cast body that mutates options
|
|
11
|
+
# with facet values and delegates to super.
|
|
12
|
+
class RestrictedType < Base
|
|
13
|
+
def render
|
|
14
|
+
Templates::RESTRICTED_SIMPLE_TYPE.result(binding)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def rendered_class_name = @spec.class_name
|
|
20
|
+
def parent_class = @spec.parent_class
|
|
21
|
+
|
|
22
|
+
def xml_namespace_line
|
|
23
|
+
ns = @spec.namespace_class_name
|
|
24
|
+
ns && "namespace #{ns}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def restricted_simple_type_required_files = required_files_block
|
|
28
|
+
|
|
29
|
+
def restricted_simple_type_cast_body
|
|
30
|
+
[
|
|
31
|
+
render_min_max,
|
|
32
|
+
render_pattern,
|
|
33
|
+
render_enumerations,
|
|
34
|
+
render_transform,
|
|
35
|
+
].compact.join
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def render_min_max
|
|
39
|
+
f = @spec.facets
|
|
40
|
+
max = f.max_inclusive || f.max_exclusive
|
|
41
|
+
min = f.min_inclusive || f.min_exclusive
|
|
42
|
+
return nil unless max || min
|
|
43
|
+
|
|
44
|
+
out = +""
|
|
45
|
+
out << "#{@extended_indent}options[:max] = #{max}\n" if max
|
|
46
|
+
out << "#{@extended_indent}options[:min] = #{min}\n" if min
|
|
47
|
+
out
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def render_pattern
|
|
51
|
+
p = @spec.facets.pattern
|
|
52
|
+
p && "#{@extended_indent}options[:pattern] = %r{#{p}}\n"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def render_enumerations
|
|
56
|
+
e = @spec.facets.enumerations
|
|
57
|
+
return nil if e.nil? || e.empty?
|
|
58
|
+
|
|
59
|
+
casted = e.map { |v| "super(#{v.inspect})" }.join(", ")
|
|
60
|
+
"#{@extended_indent}options[:values] = [#{casted}]\n"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def render_transform
|
|
64
|
+
t = @spec.transform_facet
|
|
65
|
+
t && "#{@extended_indent}value = #{t.expression}\n"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def registration_methods
|
|
69
|
+
Registration.methods_block(
|
|
70
|
+
class_name: @spec.class_name,
|
|
71
|
+
module_namespace: @module_namespace,
|
|
72
|
+
indent: @indent,
|
|
73
|
+
lazy: true,
|
|
74
|
+
keep_when_namespaced: @spec.keep_register_when_namespaced,
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def registration_execution
|
|
79
|
+
Registration.execution_line(
|
|
80
|
+
class_name: @spec.class_name,
|
|
81
|
+
module_namespace: @module_namespace,
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module Model
|
|
7
|
+
module Schema
|
|
8
|
+
module Renderers
|
|
9
|
+
# Renders a Definitions::UnionType into a
|
|
10
|
+
# Lutaml::Model::Type::Value subclass with a cast body whose
|
|
11
|
+
# strategy is chosen from the spec.
|
|
12
|
+
class Union < Base
|
|
13
|
+
def render
|
|
14
|
+
Templates::UNION_TYPE.result(binding)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def rendered_class_name = @spec.class_name
|
|
20
|
+
|
|
21
|
+
def union_required_files = required_files_block
|
|
22
|
+
|
|
23
|
+
def union_cast_body
|
|
24
|
+
case @spec.cast_strategy
|
|
25
|
+
when :resolve_type then resolve_type_body
|
|
26
|
+
when :class_refs then class_refs_body
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def resolve_type_body
|
|
31
|
+
chain = @spec.members.map { |m| resolve_type_call(m) }
|
|
32
|
+
"#{@extended_indent}#{chain.join(" ||\n ")}\n"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def resolve_type_call(type_ref)
|
|
36
|
+
"Lutaml::Model::GlobalContext.resolve_type(:#{type_ref.value}, @register).cast(value, options)"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def class_refs_body
|
|
40
|
+
classes = @spec.members.map { |m| literal_class(m) }.join(", ")
|
|
41
|
+
sp2 = @extended_indent
|
|
42
|
+
<<~BODY
|
|
43
|
+
#{sp2}[#{classes}].each do |t|
|
|
44
|
+
#{sp2} begin
|
|
45
|
+
#{sp2} casted = t.cast(value, options)
|
|
46
|
+
#{sp2} return casted unless casted.nil?
|
|
47
|
+
#{sp2} rescue StandardError
|
|
48
|
+
#{sp2} next
|
|
49
|
+
#{sp2} end
|
|
50
|
+
#{sp2}end
|
|
51
|
+
#{sp2}value
|
|
52
|
+
BODY
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def literal_class(type_ref)
|
|
56
|
+
case type_ref.kind
|
|
57
|
+
when :class_ref then type_ref.value
|
|
58
|
+
when :w3c then "::#{type_ref.value}"
|
|
59
|
+
when :symbol then "Lutaml::Model::GlobalContext.resolve_type(:#{type_ref.value})"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def registration_methods
|
|
64
|
+
Registration.methods_block(
|
|
65
|
+
class_name: @spec.class_name,
|
|
66
|
+
module_namespace: @module_namespace,
|
|
67
|
+
indent: @indent,
|
|
68
|
+
lazy: @spec.lazy_register,
|
|
69
|
+
keep_when_namespaced: @spec.keep_register_when_namespaced,
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def registration_execution
|
|
74
|
+
Registration.execution_line(
|
|
75
|
+
class_name: @spec.class_name,
|
|
76
|
+
module_namespace: @module_namespace,
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Renderers
|
|
7
|
+
autoload :Base, "#{__dir__}/renderers/base"
|
|
8
|
+
autoload :Registration, "#{__dir__}/renderers/registration"
|
|
9
|
+
autoload :RequiredFilesCalculator, "#{__dir__}/renderers/required_files_calculator"
|
|
10
|
+
autoload :MemberDecls, "#{__dir__}/renderers/member_decls"
|
|
11
|
+
autoload :Mappings, "#{__dir__}/renderers/mappings"
|
|
12
|
+
autoload :Model, "#{__dir__}/renderers/model"
|
|
13
|
+
autoload :RestrictedType, "#{__dir__}/renderers/restricted_type"
|
|
14
|
+
autoload :Union, "#{__dir__}/renderers/union"
|
|
15
|
+
autoload :Namespace, "#{__dir__}/renderers/namespace"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|