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.
Files changed (195) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/downstream-performance.yml +27 -1
  3. data/.github/workflows/js-pr-check.yml +102 -0
  4. data/.github/workflows/js-sync.yml +51 -0
  5. data/.github/workflows/opal.yml +26 -6
  6. data/.github/workflows/performance.yml +32 -7
  7. data/.github/workflows/rake.yml +78 -32
  8. data/.gitmodules +6 -0
  9. data/.rubocop_todo.yml +8 -0
  10. data/Gemfile +16 -1
  11. data/README.adoc +267 -0
  12. data/Rakefile +166 -4
  13. data/docs/_guides/builder-dsl.adoc +127 -0
  14. data/docs/_guides/schema-import.adoc +42 -0
  15. data/docs/_guides/xml-mapping.adoc +21 -0
  16. data/docs/_pages/validation.adoc +65 -0
  17. data/lib/compat/opal/generate_boot.rb +123 -0
  18. data/lib/compat/opal/js_bundle_entry.rb +42 -0
  19. data/lib/compat/opal/lutaml_model_boot.rb +497 -0
  20. data/lib/compat/opal/moxml_boot.rb +68 -0
  21. data/lib/compat/opal/yaml_compat.rb +32 -0
  22. data/lib/lutaml/json.rb +1 -1
  23. data/lib/lutaml/key_value/transform.rb +3 -8
  24. data/lib/lutaml/key_value/transformation/value_serializer.rb +14 -1
  25. data/lib/lutaml/key_value/transformation.rb +17 -5
  26. data/lib/lutaml/model/adapter_resolver.rb +2 -2
  27. data/lib/lutaml/model/attribute.rb +88 -11
  28. data/lib/lutaml/model/cached_type_resolver.rb +1 -1
  29. data/lib/lutaml/model/error/union_schema_unsupported_error.rb +11 -0
  30. data/lib/lutaml/model/global_context.rb +11 -8
  31. data/lib/lutaml/model/instrumentation.rb +2 -2
  32. data/lib/lutaml/model/mapping/mapping_rule.rb +0 -6
  33. data/lib/lutaml/model/register.rb +2 -2
  34. data/lib/lutaml/model/runtime_compatibility.rb +50 -0
  35. data/lib/lutaml/model/schema/class_loader.rb +58 -0
  36. data/lib/lutaml/model/schema/compiled_output.rb +83 -0
  37. data/lib/lutaml/model/schema/definitions/attribute.rb +32 -0
  38. data/lib/lutaml/model/schema/definitions/choice.rb +20 -0
  39. data/lib/lutaml/model/schema/definitions/facet.rb +40 -0
  40. data/lib/lutaml/model/schema/definitions/group_import.rb +22 -0
  41. data/lib/lutaml/model/schema/definitions/member_walk.rb +33 -0
  42. data/lib/lutaml/model/schema/definitions/model.rb +40 -0
  43. data/lib/lutaml/model/schema/definitions/namespace.rb +23 -0
  44. data/lib/lutaml/model/schema/definitions/restricted_type.rb +30 -0
  45. data/lib/lutaml/model/schema/definitions/sequence.rb +19 -0
  46. data/lib/lutaml/model/schema/definitions/simple_content.rb +22 -0
  47. data/lib/lutaml/model/schema/definitions/transform_facet.rb +21 -0
  48. data/lib/lutaml/model/schema/definitions/type_ref.rb +20 -0
  49. data/lib/lutaml/model/schema/definitions/union_type.rb +28 -0
  50. data/lib/lutaml/model/schema/definitions/xml_root.rb +20 -0
  51. data/lib/lutaml/model/schema/definitions.rb +24 -0
  52. data/lib/lutaml/model/schema/file_writer.rb +70 -0
  53. data/lib/lutaml/model/schema/generator/definitions_collection.rb +16 -2
  54. data/lib/lutaml/model/schema/generator/property.rb +21 -4
  55. data/lib/lutaml/model/schema/module_nesting.rb +31 -0
  56. data/lib/lutaml/model/schema/namespace_naming.rb +46 -0
  57. data/lib/lutaml/model/schema/registry_generator.rb +115 -0
  58. data/lib/lutaml/model/schema/renderers/base.rb +40 -0
  59. data/lib/lutaml/model/schema/renderers/mappings.rb +72 -0
  60. data/lib/lutaml/model/schema/renderers/member_decls.rb +115 -0
  61. data/lib/lutaml/model/schema/renderers/model.rb +121 -0
  62. data/lib/lutaml/model/schema/renderers/namespace.rb +30 -0
  63. data/lib/lutaml/model/schema/renderers/registration.rb +64 -0
  64. data/lib/lutaml/model/schema/renderers/required_files_calculator.rb +93 -0
  65. data/lib/lutaml/model/schema/renderers/restricted_type.rb +88 -0
  66. data/lib/lutaml/model/schema/renderers/union.rb +83 -0
  67. data/lib/lutaml/model/schema/renderers.rb +19 -0
  68. data/lib/lutaml/model/schema/rnc_compiler/source_resolver.rb +47 -0
  69. data/lib/lutaml/model/schema/rnc_compiler.rb +62 -0
  70. data/lib/lutaml/model/schema/rng_compiler/define_classifier.rb +91 -0
  71. data/lib/lutaml/model/schema/rng_compiler/element_visitor.rb +392 -0
  72. data/lib/lutaml/model/schema/rng_compiler/member_collector.rb +30 -0
  73. data/lib/lutaml/model/schema/rng_compiler/rng_helpers.rb +152 -0
  74. data/lib/lutaml/model/schema/rng_compiler/value_type_resolver.rb +125 -0
  75. data/lib/lutaml/model/schema/rng_compiler.rb +220 -0
  76. data/lib/lutaml/model/schema/templates.rb +162 -0
  77. data/lib/lutaml/model/schema/xml_compiler/element_order.rb +41 -0
  78. data/lib/lutaml/model/schema/xml_compiler/registry_generator.rb +31 -107
  79. data/lib/lutaml/model/schema/xml_compiler/spec_builder/complex_types.rb +191 -0
  80. data/lib/lutaml/model/schema/xml_compiler/spec_builder/members.rb +216 -0
  81. data/lib/lutaml/model/schema/xml_compiler/spec_builder/simple_types.rb +153 -0
  82. data/lib/lutaml/model/schema/xml_compiler/spec_builder.rb +167 -0
  83. data/lib/lutaml/model/schema/xml_compiler/supported_data_types.rb +87 -0
  84. data/lib/lutaml/model/schema/xml_compiler.rb +38 -517
  85. data/lib/lutaml/model/schema.rb +19 -0
  86. data/lib/lutaml/model/serialize/attribute_definition.rb +23 -11
  87. data/lib/lutaml/model/serialize/builder.rb +70 -2
  88. data/lib/lutaml/model/serialize/initialization.rb +3 -2
  89. data/lib/lutaml/model/serialize/value_mapping.rb +9 -57
  90. data/lib/lutaml/model/serialize.rb +6 -9
  91. data/lib/lutaml/model/services/rule_value_extractor.rb +2 -3
  92. data/lib/lutaml/model/services.rb +0 -2
  93. data/lib/lutaml/model/store.rb +2 -1
  94. data/lib/lutaml/model/toml.rb +1 -1
  95. data/lib/lutaml/model/transform.rb +17 -39
  96. data/lib/lutaml/model/transformation_registry.rb +1 -1
  97. data/lib/lutaml/model/type.rb +1 -0
  98. data/lib/lutaml/model/union.rb +320 -0
  99. data/lib/lutaml/model/utils.rb +6 -0
  100. data/lib/lutaml/model/validation.rb +1 -4
  101. data/lib/lutaml/model/version.rb +1 -1
  102. data/lib/lutaml/model.rb +18 -0
  103. data/lib/lutaml/toml.rb +1 -1
  104. data/lib/lutaml/xml/adapter.rb +1 -1
  105. data/lib/lutaml/xml/adapter_loader.rb +1 -1
  106. data/lib/lutaml/xml/builder/base.rb +1 -1
  107. data/lib/lutaml/xml/builder.rb +1 -1
  108. data/lib/lutaml/xml/error/schema_validation_error.rb +25 -0
  109. data/lib/lutaml/xml/model_transform.rb +5 -1
  110. data/lib/lutaml/xml/schema/relaxng_schema.rb +5 -0
  111. data/lib/lutaml/xml/schema/xsd_schema.rb +4 -0
  112. data/lib/lutaml/xml/serialization/format_conversion.rb +48 -0
  113. data/lib/lutaml/xml/serialization/instance_methods.rb +13 -0
  114. data/lib/lutaml/xml/transformation/element_builder.rb +21 -8
  115. data/lib/lutaml/xml/transformation/ordered_applier.rb +104 -4
  116. data/lib/lutaml/xml/xsd_validator.rb +66 -0
  117. data/lib/lutaml/xml.rb +53 -12
  118. data/lutaml-model.gemspec +4 -2
  119. data/spec/fixtures/xml/schema/rnc/address_book.rnc +15 -0
  120. data/spec/fixtures/xml/schema/rnc/book_features.rnc +18 -0
  121. data/spec/fixtures/xml/schema/rnc/includes/book.rnc +6 -0
  122. data/spec/fixtures/xml/schema/rnc/includes/circular_a.rnc +8 -0
  123. data/spec/fixtures/xml/schema/rnc/includes/circular_b.rnc +8 -0
  124. data/spec/fixtures/xml/schema/rnc/includes/library.rnc +8 -0
  125. data/spec/fixtures/xml/schema/rng/address_book.rng +16 -0
  126. data/spec/fixtures/xml/schema/rng/book.rng +27 -0
  127. data/spec/fixtures/xml/schema/rng/fixed_value.rng +11 -0
  128. data/spec/fixtures/xml/schema/rng/fragment_in_choice.rng +30 -0
  129. data/spec/fixtures/xml/schema/rng/full_name.rng +39 -0
  130. data/spec/fixtures/xml/schema/rng/integer_range.rng +32 -0
  131. data/spec/fixtures/xml/schema/rng/list_type.rng +12 -0
  132. data/spec/fixtures/xml/schema/rng/namespaced.rng +9 -0
  133. data/spec/fixtures/xml/schema/rng/paragraph.rng +13 -0
  134. data/spec/fixtures/xml/schema/rng/person.rng +23 -0
  135. data/spec/fixtures/xml/schema/rng/union.rng +14 -0
  136. data/spec/fixtures/xml/validate_xml_with_address.xsd +13 -0
  137. data/spec/fixtures/xml/validate_xml_with_contact.xsd +11 -0
  138. data/spec/fixtures/xml/validate_xml_with_person.xsd +11 -0
  139. data/spec/fixtures/xml/validate_xml_with_person_strict.xsd +17 -0
  140. data/spec/fixtures/xml/validate_xml_with_product.xsd +37 -0
  141. data/spec/lutaml/model/attribute_apply_value_map_spec.rb +134 -0
  142. data/spec/lutaml/model/attribute_spec.rb +169 -12
  143. data/spec/lutaml/model/boot_manifest_spec.rb +30 -0
  144. data/spec/lutaml/model/collection_spec.rb +18 -0
  145. data/spec/lutaml/model/mixed_content_spec.rb +171 -0
  146. data/spec/lutaml/model/opal_smoke_spec.rb +67 -2
  147. data/spec/lutaml/model/ordered_content_spec.rb +41 -0
  148. data/spec/lutaml/model/raw_element_spec.rb +125 -0
  149. data/spec/lutaml/model/rule_value_extractor_spec.rb +7 -14
  150. data/spec/lutaml/model/runtime_compatibility_spec.rb +25 -0
  151. data/spec/lutaml/model/schema/renderers/model_spec.rb +149 -0
  152. data/spec/lutaml/model/schema/renderers/namespace_spec.rb +36 -0
  153. data/spec/lutaml/model/schema/renderers/restricted_type_spec.rb +75 -0
  154. data/spec/lutaml/model/schema/renderers/union_spec.rb +58 -0
  155. data/spec/lutaml/model/serialize/builder_spec.rb +358 -0
  156. data/spec/lutaml/model/transform_apply_value_map_spec.rb +64 -0
  157. data/spec/lutaml/model/union_attribute_spec.rb +658 -0
  158. data/spec/lutaml/model/union_spec.rb +257 -0
  159. data/spec/lutaml/model/utils_spec.rb +14 -0
  160. data/spec/lutaml/xml/opal_xml_spec.rb +22 -20
  161. data/spec/lutaml/xml/schema/compiler_spec.rb +21 -4
  162. data/spec/lutaml/xml/schema/rnc_compiler_spec.rb +404 -0
  163. data/spec/lutaml/xml/schema/rng_compiler_spec.rb +893 -0
  164. data/spec/lutaml/xml/validate_xml_with_constructs_spec.rb +222 -0
  165. data/spec/lutaml/xml/validate_xml_with_spec.rb +186 -0
  166. data/spec/spec_helper.rb +28 -11
  167. data/spec/support/opal.rb +5 -2
  168. metadata +99 -31
  169. data/lib/lutaml/model/schema/xml_compiler/attribute.rb +0 -106
  170. data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +0 -47
  171. data/lib/lutaml/model/schema/xml_compiler/choice.rb +0 -67
  172. data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +0 -27
  173. data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +0 -34
  174. data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +0 -173
  175. data/lib/lutaml/model/schema/xml_compiler/element.rb +0 -121
  176. data/lib/lutaml/model/schema/xml_compiler/group.rb +0 -113
  177. data/lib/lutaml/model/schema/xml_compiler/restriction.rb +0 -104
  178. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +0 -52
  179. data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +0 -40
  180. data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +0 -265
  181. data/lib/lutaml/model/schema/xml_compiler/xml_namespace_class.rb +0 -110
  182. data/lib/lutaml/model/services/default_value_resolver.rb +0 -60
  183. data/spec/lutaml/model/services/default_value_resolver_spec.rb +0 -162
  184. data/spec/lutaml/xml/schema/compiler/attribute_group_spec.rb +0 -71
  185. data/spec/lutaml/xml/schema/compiler/attribute_spec.rb +0 -67
  186. data/spec/lutaml/xml/schema/compiler/choice_spec.rb +0 -74
  187. data/spec/lutaml/xml/schema/compiler/complex_content_restriction_spec.rb +0 -60
  188. data/spec/lutaml/xml/schema/compiler/complex_content_spec.rb +0 -41
  189. data/spec/lutaml/xml/schema/compiler/complex_type_spec.rb +0 -202
  190. data/spec/lutaml/xml/schema/compiler/element_spec.rb +0 -68
  191. data/spec/lutaml/xml/schema/compiler/group_spec.rb +0 -90
  192. data/spec/lutaml/xml/schema/compiler/restriction_spec.rb +0 -77
  193. data/spec/lutaml/xml/schema/compiler/sequence_spec.rb +0 -65
  194. data/spec/lutaml/xml/schema/compiler/simple_content_spec.rb +0 -61
  195. 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