lutaml-model 0.7.3 → 0.7.5

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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +1 -0
  3. data/.github/workflows/release.yml +3 -0
  4. data/.gitignore +6 -1
  5. data/.irbrc +1 -0
  6. data/.pryrc +1 -0
  7. data/.rubocop_todo.yml +25 -52
  8. data/README.adoc +2177 -192
  9. data/docs/custom_registers.adoc +228 -0
  10. data/docs/schema_generation.adoc +898 -0
  11. data/docs/schema_import.adoc +364 -0
  12. data/flake.lock +114 -0
  13. data/flake.nix +103 -0
  14. data/lib/lutaml/model/attribute.rb +230 -94
  15. data/lib/lutaml/model/choice.rb +30 -0
  16. data/lib/lutaml/model/collection.rb +194 -0
  17. data/lib/lutaml/model/comparable_model.rb +3 -3
  18. data/lib/lutaml/model/config.rb +26 -3
  19. data/lib/lutaml/model/constants.rb +2 -0
  20. data/lib/lutaml/model/error/element_count_out_of_range_error.rb +29 -0
  21. data/lib/lutaml/model/error/invalid_attribute_name_error.rb +15 -0
  22. data/lib/lutaml/model/error/invalid_attribute_options_error.rb +16 -0
  23. data/lib/lutaml/model/error/invalid_choice_range_error.rb +3 -5
  24. data/lib/lutaml/model/error/register/not_registrable_class_error.rb +11 -0
  25. data/lib/lutaml/model/error/type/invalid_value_error.rb +5 -3
  26. data/lib/lutaml/model/error/type/max_bound_error.rb +20 -0
  27. data/lib/lutaml/model/error/type/max_length_error.rb +20 -0
  28. data/lib/lutaml/model/error/type/min_bound_error.rb +20 -0
  29. data/lib/lutaml/model/error/type/min_length_error.rb +20 -0
  30. data/lib/lutaml/model/error/type/pattern_not_matched_error.rb +18 -0
  31. data/lib/lutaml/model/error/validation_failed_error.rb +9 -0
  32. data/lib/lutaml/model/error.rb +10 -0
  33. data/lib/lutaml/model/errors.rb +36 -0
  34. data/lib/lutaml/model/format_registry.rb +5 -2
  35. data/lib/lutaml/model/global_register.rb +41 -0
  36. data/lib/lutaml/model/{hash.rb → hash_adapter.rb} +5 -5
  37. data/lib/lutaml/model/jsonl/document.rb +14 -0
  38. data/lib/lutaml/model/jsonl/mapping.rb +19 -0
  39. data/lib/lutaml/model/jsonl/mapping_rule.rb +9 -0
  40. data/lib/lutaml/model/jsonl/standard_adapter.rb +33 -0
  41. data/lib/lutaml/model/jsonl/transform.rb +19 -0
  42. data/lib/lutaml/model/jsonl.rb +21 -0
  43. data/lib/lutaml/model/key_value_document.rb +3 -2
  44. data/lib/lutaml/model/mapping/key_value_mapping.rb +64 -4
  45. data/lib/lutaml/model/mapping/key_value_mapping_rule.rb +4 -0
  46. data/lib/lutaml/model/mapping/mapping_rule.rb +8 -3
  47. data/lib/lutaml/model/register.rb +105 -0
  48. data/lib/lutaml/model/registrable.rb +6 -0
  49. data/lib/lutaml/model/schema/base_schema.rb +64 -0
  50. data/lib/lutaml/model/schema/decorators/attribute.rb +114 -0
  51. data/lib/lutaml/model/schema/decorators/choices.rb +31 -0
  52. data/lib/lutaml/model/schema/decorators/class_definition.rb +85 -0
  53. data/lib/lutaml/model/schema/decorators/definition_collection.rb +97 -0
  54. data/lib/lutaml/model/schema/generator/definition.rb +53 -0
  55. data/lib/lutaml/model/schema/generator/definitions_collection.rb +81 -0
  56. data/lib/lutaml/model/schema/generator/properties_collection.rb +63 -0
  57. data/lib/lutaml/model/schema/generator/property.rb +110 -0
  58. data/lib/lutaml/model/schema/generator/ref.rb +24 -0
  59. data/lib/lutaml/model/schema/helpers/template_helper.rb +49 -0
  60. data/lib/lutaml/model/schema/json_schema.rb +42 -49
  61. data/lib/lutaml/model/schema/relaxng_schema.rb +14 -10
  62. data/lib/lutaml/model/schema/renderer.rb +36 -0
  63. data/lib/lutaml/model/schema/shared_methods.rb +24 -0
  64. data/lib/lutaml/model/schema/templates/model.erb +9 -0
  65. data/lib/lutaml/model/schema/xml_compiler/attribute.rb +85 -0
  66. data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +45 -0
  67. data/lib/lutaml/model/schema/xml_compiler/choice.rb +65 -0
  68. data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +27 -0
  69. data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +34 -0
  70. data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +136 -0
  71. data/lib/lutaml/model/schema/xml_compiler/element.rb +104 -0
  72. data/lib/lutaml/model/schema/xml_compiler/group.rb +97 -0
  73. data/lib/lutaml/model/schema/xml_compiler/restriction.rb +101 -0
  74. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +50 -0
  75. data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +36 -0
  76. data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +189 -0
  77. data/lib/lutaml/model/schema/xml_compiler.rb +231 -587
  78. data/lib/lutaml/model/schema/xsd_schema.rb +12 -8
  79. data/lib/lutaml/model/schema/yaml_schema.rb +41 -35
  80. data/lib/lutaml/model/schema.rb +1 -0
  81. data/lib/lutaml/model/sequence.rb +60 -30
  82. data/lib/lutaml/model/serialize.rb +175 -53
  83. data/lib/lutaml/model/services/base.rb +11 -0
  84. data/lib/lutaml/model/services/logger.rb +2 -2
  85. data/lib/lutaml/model/services/rule_value_extractor.rb +92 -0
  86. data/lib/lutaml/model/services/type/validator/number.rb +25 -0
  87. data/lib/lutaml/model/services/type/validator/string.rb +52 -0
  88. data/lib/lutaml/model/services/type/validator.rb +43 -0
  89. data/lib/lutaml/model/services/validator.rb +145 -0
  90. data/lib/lutaml/model/services.rb +3 -0
  91. data/lib/lutaml/model/transform/key_value_transform.rb +60 -50
  92. data/lib/lutaml/model/transform/xml_transform.rb +46 -57
  93. data/lib/lutaml/model/transform.rb +22 -8
  94. data/lib/lutaml/model/type/boolean.rb +1 -1
  95. data/lib/lutaml/model/type/date.rb +1 -1
  96. data/lib/lutaml/model/type/date_time.rb +1 -1
  97. data/lib/lutaml/model/type/decimal.rb +11 -9
  98. data/lib/lutaml/model/type/float.rb +2 -1
  99. data/lib/lutaml/model/type/integer.rb +24 -21
  100. data/lib/lutaml/model/type/string.rb +4 -2
  101. data/lib/lutaml/model/type/time.rb +1 -1
  102. data/lib/lutaml/model/type/time_without_date.rb +1 -1
  103. data/lib/lutaml/model/type/value.rb +5 -1
  104. data/lib/lutaml/model/type.rb +5 -2
  105. data/lib/lutaml/model/utils.rb +30 -8
  106. data/lib/lutaml/model/validation.rb +6 -4
  107. data/lib/lutaml/model/version.rb +1 -1
  108. data/lib/lutaml/model/xml/document.rb +37 -19
  109. data/lib/lutaml/model/xml/mapping.rb +74 -13
  110. data/lib/lutaml/model/xml/mapping_rule.rb +10 -2
  111. data/lib/lutaml/model/xml/nokogiri_adapter.rb +5 -3
  112. data/lib/lutaml/model/xml/oga/element.rb +4 -1
  113. data/lib/lutaml/model/xml/oga_adapter.rb +4 -3
  114. data/lib/lutaml/model/xml/ox_adapter.rb +20 -6
  115. data/lib/lutaml/model/xml/xml_element.rb +3 -28
  116. data/lib/lutaml/model/xml_adapter/element.rb +1 -1
  117. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +1 -1
  118. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +1 -1
  119. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +1 -1
  120. data/lib/lutaml/model/yamls/document.rb +14 -0
  121. data/lib/lutaml/model/yamls/mapping.rb +19 -0
  122. data/lib/lutaml/model/yamls/mapping_rule.rb +9 -0
  123. data/lib/lutaml/model/yamls/standard_adapter.rb +34 -0
  124. data/lib/lutaml/model/yamls/transform.rb +19 -0
  125. data/lib/lutaml/model/yamls.rb +21 -0
  126. data/lib/lutaml/model.rb +7 -31
  127. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +4 -5
  128. data/spec/fixtures/xml/advanced_test_schema.xsd +134 -0
  129. data/spec/fixtures/xml/examples/nested_categories.xml +55 -0
  130. data/spec/fixtures/xml/examples/valid_catalog.xml +43 -0
  131. data/spec/fixtures/xml/product_catalog.xsd +151 -0
  132. data/spec/fixtures/xml/specifications_schema.xsd +38 -0
  133. data/spec/lutaml/model/attribute_collection_spec.rb +101 -0
  134. data/spec/lutaml/model/attribute_spec.rb +41 -44
  135. data/spec/lutaml/model/choice_spec.rb +44 -0
  136. data/spec/lutaml/model/custom_collection_spec.rb +830 -0
  137. data/spec/lutaml/model/custom_model_spec.rb +15 -3
  138. data/spec/lutaml/model/defaults_spec.rb +5 -1
  139. data/spec/lutaml/model/global_register_spec.rb +108 -0
  140. data/spec/lutaml/model/group_spec.rb +9 -3
  141. data/spec/lutaml/model/jsonl/standard_adapter_spec.rb +91 -0
  142. data/spec/lutaml/model/jsonl_spec.rb +229 -0
  143. data/spec/lutaml/model/multiple_mapping_spec.rb +1 -1
  144. data/spec/lutaml/model/register/key_value_spec.rb +275 -0
  145. data/spec/lutaml/model/register/xml_spec.rb +187 -0
  146. data/spec/lutaml/model/register_spec.rb +147 -0
  147. data/spec/lutaml/model/rule_value_extractor_spec.rb +162 -0
  148. data/spec/lutaml/model/schema/generator/definitions_collection_spec.rb +120 -0
  149. data/spec/lutaml/model/schema/json_schema_spec.rb +412 -51
  150. data/spec/lutaml/model/schema/json_schema_to_models_spec.rb +383 -0
  151. data/spec/lutaml/model/schema/xml_compiler/attribute_group_spec.rb +65 -0
  152. data/spec/lutaml/model/schema/xml_compiler/attribute_spec.rb +63 -0
  153. data/spec/lutaml/model/schema/xml_compiler/choice_spec.rb +71 -0
  154. data/spec/lutaml/model/schema/xml_compiler/complex_content_restriction_spec.rb +55 -0
  155. data/spec/lutaml/model/schema/xml_compiler/complex_content_spec.rb +37 -0
  156. data/spec/lutaml/model/schema/xml_compiler/complex_type_spec.rb +173 -0
  157. data/spec/lutaml/model/schema/xml_compiler/element_spec.rb +63 -0
  158. data/spec/lutaml/model/schema/xml_compiler/group_spec.rb +86 -0
  159. data/spec/lutaml/model/schema/xml_compiler/restriction_spec.rb +76 -0
  160. data/spec/lutaml/model/schema/xml_compiler/sequence_spec.rb +59 -0
  161. data/spec/lutaml/model/schema/xml_compiler/simple_content_spec.rb +55 -0
  162. data/spec/lutaml/model/schema/xml_compiler/simple_type_spec.rb +181 -0
  163. data/spec/lutaml/model/schema/xml_compiler_spec.rb +503 -1804
  164. data/spec/lutaml/model/schema/yaml_schema_spec.rb +249 -26
  165. data/spec/lutaml/model/sequence_spec.rb +36 -0
  166. data/spec/lutaml/model/serializable_spec.rb +31 -0
  167. data/spec/lutaml/model/type_spec.rb +8 -4
  168. data/spec/lutaml/model/utils_spec.rb +3 -3
  169. data/spec/lutaml/model/xml/derived_attributes_spec.rb +1 -1
  170. data/spec/lutaml/model/xml/xml_element_spec.rb +7 -1
  171. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +6 -6
  172. data/spec/lutaml/model/xml_adapter_spec.rb +24 -0
  173. data/spec/lutaml/model/xml_mapping_rule_spec.rb +11 -4
  174. data/spec/lutaml/model/xml_mapping_spec.rb +1 -1
  175. data/spec/lutaml/model/yamls/standard_adapter_spec.rb +183 -0
  176. data/spec/lutaml/model/yamls_spec.rb +294 -0
  177. data/spec/spec_helper.rb +1 -0
  178. metadata +105 -9
  179. data/lib/lutaml/model/schema/templates/simple_type.rb +0 -247
  180. /data/lib/lutaml/model/{hash → hash_adapter}/document.rb +0 -0
  181. /data/lib/lutaml/model/{hash → hash_adapter}/mapping.rb +0 -0
  182. /data/lib/lutaml/model/{hash → hash_adapter}/mapping_rule.rb +0 -0
  183. /data/lib/lutaml/model/{hash → hash_adapter}/standard_adapter.rb +0 -0
  184. /data/lib/lutaml/model/{hash → hash_adapter}/transform.rb +0 -0
@@ -4,11 +4,14 @@ module Lutaml
4
4
  module Model
5
5
  module Schema
6
6
  class RelaxngSchema
7
+ extend SharedMethods
8
+
7
9
  def self.generate(klass, options = {})
10
+ register = extract_register_from(klass)
8
11
  builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
9
12
  xml.grammar(xmlns: "http://relaxng.org/ns/structure/1.0") do
10
13
  generate_start(xml, klass)
11
- generate_define(xml, klass)
14
+ generate_define(xml, klass, register)
12
15
  end
13
16
  end
14
17
 
@@ -21,34 +24,35 @@ module Lutaml
21
24
  end
22
25
  end
23
26
 
24
- def self.generate_attributes(xml, klass)
27
+ def self.generate_attributes(xml, klass, register)
25
28
  klass.attributes.each do |name, attr|
26
- if attr.type <= Lutaml::Model::Serialize
27
- xml.ref(name: attr.type.name)
29
+ attr_type = attr.type(register)
30
+ if attr_type <= Lutaml::Model::Serialize
31
+ xml.ref(name: attr_type.name)
28
32
  elsif attr.collection?
29
33
  xml.zeroOrMore do
30
34
  xml.element(name: name) do
31
- xml.data(type: get_relaxng_type(attr.type))
35
+ xml.data(type: get_relaxng_type(attr_type))
32
36
  end
33
37
  end
34
38
  else
35
39
  xml.element(name: name) do
36
- xml.data(type: get_relaxng_type(attr.type))
40
+ xml.data(type: get_relaxng_type(attr_type))
37
41
  end
38
42
  end
39
43
  end
40
44
  end
41
45
 
42
- def self.generate_define(xml, klass)
46
+ def self.generate_define(xml, klass, register)
43
47
  xml.define(name: klass.name) do
44
48
  xml.element(name: klass.name) do
45
- generate_attributes(xml, klass)
49
+ generate_attributes(xml, klass, register)
46
50
  end
47
51
  end
48
52
 
49
53
  klass.attributes.each_value do |attr|
50
- if attr.type <= Lutaml::Model::Serialize
51
- generate_define(xml, attr.type)
54
+ if attr.type(register) <= Lutaml::Model::Serialize
55
+ generate_define(xml, attr.type(register), register)
52
56
  end
53
57
  end
54
58
  end
@@ -0,0 +1,36 @@
1
+ require "erb"
2
+ require "ostruct"
3
+
4
+ require_relative "helpers/template_helper"
5
+
6
+ module Lutaml
7
+ module Model
8
+ module Schema
9
+ class Context
10
+ include Lutaml::Model::Schema::Helpers::TemplateHelper
11
+
12
+ attr_reader :schema
13
+
14
+ def initialize(schema)
15
+ @schema = schema
16
+ end
17
+ end
18
+
19
+ class Renderer
20
+ def self.render(template_path, context = {})
21
+ new(template_path).render(context)
22
+ end
23
+
24
+ def initialize(template_path)
25
+ @template = File.read(template_path)
26
+ end
27
+
28
+ def render(context = {})
29
+ context = Context.new(context[:schema])
30
+
31
+ ERB.new(@template, trim_mode: "-").result(context.instance_eval { binding })
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module SharedMethods
7
+ def extract_register_from(klass)
8
+ register = if klass.class_variable_defined?(:@@register)
9
+ klass.class_variable_get(:@@register)
10
+ end
11
+
12
+ case register
13
+ when Lutaml::Model::Register
14
+ register.id
15
+ when String, Symbol
16
+ register.to_sym
17
+ else
18
+ Lutaml::Model::Config.default_register
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,9 @@
1
+ require "lutaml/model"
2
+
3
+ <%= open_namespaces(schema.namespaces) %>
4
+ <%= indent(schema.namespaces.size) %>class <%= schema.name %> < <%= schema.parent_class %>
5
+ <% schema.attributes.each do |attribute| -%>
6
+ <%= attribute_line(attribute, schema.namespaces.size + 1) %>
7
+ <% end -%>
8
+ <%= indent(schema.namespaces.size) %>end
9
+ <%= close_namespaces(schema.namespaces) %>
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module XmlCompiler
7
+ class Attribute
8
+ attr_accessor :id,
9
+ :ref,
10
+ :name,
11
+ :type,
12
+ :default
13
+
14
+ TEMPLATE = ERB.new(<<~TEMPLATE, trim_mode: "-")
15
+ <%= indent %>attribute :<%= resolved_name %>, :<%= resolved_type %>
16
+ TEMPLATE
17
+
18
+ XML_MAPPING_TEMPLATE = ERB.new(<<~XML_MAPPING_TEMPLATE, trim_mode: "-")
19
+ <%= indent %>map_attribute :<%= resolved_name(change_case: false) %>, to: :<%= resolved_name %><%= namespace_prefix_option %>
20
+ XML_MAPPING_TEMPLATE
21
+
22
+ def initialize(name: nil, ref: nil)
23
+ @name = name
24
+ @ref = ref
25
+ end
26
+
27
+ def to_attributes(indent)
28
+ return if skippable?
29
+
30
+ TEMPLATE.result(binding)
31
+ end
32
+
33
+ def to_xml_mapping(indent)
34
+ return if skippable?
35
+
36
+ XML_MAPPING_TEMPLATE.result(binding)
37
+ end
38
+
39
+ def required_files
40
+ return if skippable?
41
+
42
+ raw_type = resolved_type(change_case: false)
43
+ if raw_type == "decimal"
44
+ "require \"bigdecimal\""
45
+ elsif !SimpleType.skippable?(raw_type)
46
+ "require_relative \"#{Utils.snake_case(raw_type)}\""
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def resolved_name(change_case: true)
53
+ @current_name ||= name || referenced_instance&.name
54
+ change_case ? Utils.snake_case(@current_name) : @current_name
55
+ end
56
+
57
+ def resolved_type(change_case: true)
58
+ @current_type ||= type || referenced_instance&.type
59
+ klass_name = last_of_split(@current_type)
60
+ change_case ? Utils.snake_case(klass_name) : klass_name
61
+ end
62
+
63
+ def referenced_instance
64
+ @referenced_instance ||= XmlCompiler.attributes[last_of_split]
65
+ end
66
+
67
+ def last_of_split(field = ref)
68
+ field&.split(":")&.last
69
+ end
70
+
71
+ def skippable?
72
+ resolved_name == "schema_location"
73
+ end
74
+
75
+ def namespace_prefix_option
76
+ return "" unless Utils.present?(ref)
77
+ return "" unless ref.start_with?("xml:")
78
+
79
+ ", namespace: \"http://www.w3.org/XML/1998/namespace\", prefix: \"xml\""
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module XmlCompiler
7
+ class AttributeGroup
8
+ attr_accessor :name, :ref, :instances
9
+
10
+ def initialize(name: nil, ref: nil)
11
+ @name = name
12
+ @ref = ref
13
+ @instances = []
14
+ end
15
+
16
+ def <<(instance)
17
+ return if instance.nil?
18
+
19
+ @instances << instance
20
+ end
21
+
22
+ def to_attributes(indent)
23
+ resolved_instances.map { |instance| instance.to_attributes(indent) }
24
+ end
25
+
26
+ def to_xml_mapping(indent)
27
+ resolved_instances.map { |instance| instance.to_xml_mapping(indent) }
28
+ end
29
+
30
+ def required_files
31
+ resolved_instances.map(&:required_files)
32
+ end
33
+
34
+ private
35
+
36
+ def resolved_instances
37
+ return @instances unless Utils.present?(@ref)
38
+
39
+ XmlCompiler.attribute_groups[@ref]&.instances
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module XmlCompiler
7
+ class Choice
8
+ attr_accessor :instances, :min_occurs, :max_occurs
9
+
10
+ INDENT = " "
11
+
12
+ TEMPLATE = ERB.new(<<~TEMPLATE, trim_mode: "-")
13
+ <%= indent %>choice<%= block_options %> do
14
+ <%= instances.map { |instance| instance.to_attributes(indent + INDENT) }.join -%>
15
+ <%= indent %>end
16
+ TEMPLATE
17
+
18
+ def initialize
19
+ @instances = []
20
+ end
21
+
22
+ def <<(instance)
23
+ return if instance.nil?
24
+
25
+ @instances << instance
26
+ end
27
+
28
+ def to_attributes(indent = INDENT)
29
+ TEMPLATE.result(binding)
30
+ end
31
+
32
+ def to_xml_mapping(indent = INDENT)
33
+ instances.filter_map { |instance| instance.to_xml_mapping(indent) }.join
34
+ end
35
+
36
+ def required_files
37
+ @instances.map(&:required_files).flatten.compact
38
+ end
39
+
40
+ private
41
+
42
+ def min_option
43
+ "min: #{min_occurs.nil? ? 1 : min_occurs.to_i}"
44
+ end
45
+
46
+ def max_option
47
+ value = case max_occurs
48
+ when "unbounded"
49
+ "Float::INFINITY"
50
+ when NilClass
51
+ 1
52
+ else
53
+ max_occurs.to_i
54
+ end
55
+ ", max: #{value}"
56
+ end
57
+
58
+ def block_options
59
+ ["(", min_option, max_option, ")"].compact.join
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,27 @@
1
+ module Lutaml
2
+ module Model
3
+ module Schema
4
+ module XmlCompiler
5
+ class ComplexContent
6
+ attr_accessor :restriction
7
+
8
+ def initialize(restriction = nil)
9
+ @restriction = restriction
10
+ end
11
+
12
+ def to_attributes(indent)
13
+ restriction&.to_attributes(indent)
14
+ end
15
+
16
+ def to_xml_mapping(indent)
17
+ restriction&.to_xml_mapping(indent)
18
+ end
19
+
20
+ def required_files
21
+ restriction&.required_files
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,34 @@
1
+ module Lutaml
2
+ module Model
3
+ module Schema
4
+ module XmlCompiler
5
+ class ComplexContentRestriction
6
+ attr_accessor :base, :instances
7
+
8
+ def initialize(base: nil, instances: [])
9
+ @base = base
10
+ @instances = instances
11
+ end
12
+
13
+ def <<(instance)
14
+ return if instance.nil?
15
+
16
+ @instances << instance
17
+ end
18
+
19
+ def to_attributes(indent)
20
+ instances.map { |instance| instance.to_attributes(indent) }
21
+ end
22
+
23
+ def to_xml_mapping(indent)
24
+ instances.map { |instance| instance.to_xml_mapping(indent) }
25
+ end
26
+
27
+ def required_files
28
+ instances.map(&:required_files)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,136 @@
1
+ module Lutaml
2
+ module Model
3
+ module Schema
4
+ module XmlCompiler
5
+ class ComplexType
6
+ attr_accessor :id,
7
+ :name,
8
+ :mixed,
9
+ :instances,
10
+ :base_class,
11
+ :simple_content
12
+
13
+ SERIALIZABLE_BASE_CLASS = "Lutaml::Model::Serializable".freeze
14
+
15
+ SIMPLE_CONTENT_ATTRIBUTE_TEMPLATE = ERB.new(<<~TEMPLATE, trim_mode: "-")
16
+ <%= @indent %>attribute :content, :<%= simple_content_type %>
17
+ <%= simple_content.to_attributes(@indent) if simple_content? -%>
18
+ TEMPLATE
19
+
20
+ TEMPLATE = ERB.new(<<~TEMPLATE, trim_mode: "-")
21
+ # frozen_string_literal: true
22
+
23
+ <%= required_files.uniq.join("\n") + "\n" -%>
24
+ class <%= Utils.camel_case(name) %> < <%= base_class_name %>
25
+ <%= instances.flat_map { |instance| instance.to_attributes(@indent) }.join + "\n" -%>
26
+ <%= simple_content_attribute -%>
27
+ <%= @indent %>xml do
28
+ <%= extended_indent %>root "<%= name %>"<%= root_options %>
29
+ <%= namespace_and_prefix %>
30
+ <%= "\#{extended_indent}map_content to: :content" if simple_content? || mixed %>
31
+ <%= instances.flat_map { |instance| instance.to_xml_mapping(extended_indent) }.join -%>
32
+ <%= simple_content.to_xml_mapping(extended_indent) if simple_content? -%>
33
+ <%= @indent %>end
34
+
35
+ <%= @indent %>def self.register
36
+ <%= extended_indent %>Lutaml::Model::GlobalRegister.lookup(Lutaml::Model::Config.default_register)
37
+ <%= @indent %>end
38
+
39
+ <%= @indent %>def self.register_class_with_id
40
+ <%= extended_indent %>register.register_model(self, id: :<%= Utils.snake_case(name) %>)
41
+ <%= @indent %>end
42
+ end
43
+
44
+ <%= Utils.camel_case(name) %>.register_class_with_id
45
+ TEMPLATE
46
+
47
+ def initialize(base_class: SERIALIZABLE_BASE_CLASS)
48
+ @base_class = base_class
49
+ @instances = []
50
+ end
51
+
52
+ def <<(instance)
53
+ return if instance.nil?
54
+
55
+ @instances << instance
56
+ end
57
+
58
+ def simple_content?
59
+ Utils.present?(@simple_content)
60
+ end
61
+
62
+ def to_class(options: {})
63
+ setup_options(options)
64
+ TEMPLATE.result(binding)
65
+ end
66
+
67
+ def required_files
68
+ files = [base_class_require]
69
+ files.concat(@instances.map(&:required_files).flatten.compact.uniq)
70
+ files.concat(simple_content.required_files) if simple_content?
71
+ files
72
+ end
73
+
74
+ private
75
+
76
+ def setup_options(options)
77
+ @namespace, @prefix = options.values_at(:namespace, :prefix)
78
+ @indent = " " * options&.fetch(:indent, 2)
79
+ end
80
+
81
+ def simple_content_type
82
+ return "string" unless simple_content?
83
+
84
+ Utils.snake_case(last_of_split(simple_content.base_class))
85
+ end
86
+
87
+ def simple_content_attribute
88
+ SIMPLE_CONTENT_ATTRIBUTE_TEMPLATE.result(binding) if simple_content? || mixed
89
+ end
90
+
91
+ def root_options
92
+ return "" unless mixed
93
+
94
+ ", mixed: true"
95
+ end
96
+
97
+ def namespace_and_prefix
98
+ return "" if Utils.blank?(@namespace) && Utils.blank?(@prefix)
99
+
100
+ [namespace_option, @prefix&.inspect].compact.join(", ")
101
+ end
102
+
103
+ def extended_indent
104
+ @indent * 2
105
+ end
106
+
107
+ def namespace_option
108
+ "#{extended_indent}namespace #{@namespace.inspect}"
109
+ end
110
+
111
+ def base_class_name
112
+ case base_class
113
+ when SERIALIZABLE_BASE_CLASS
114
+ SERIALIZABLE_BASE_CLASS
115
+ else
116
+ Utils.camel_case(last_of_split(base_class))
117
+ end
118
+ end
119
+
120
+ def base_class_require
121
+ case base_class
122
+ when SERIALIZABLE_BASE_CLASS
123
+ "require \"lutaml/model\""
124
+ else
125
+ "require_relative \"#{Utils.snake_case(last_of_split(base_class))}\""
126
+ end
127
+ end
128
+
129
+ def last_of_split(field)
130
+ field&.split(":")&.last
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module XmlCompiler
7
+ class Element
8
+ attr_accessor :id,
9
+ :ref,
10
+ :name,
11
+ :type,
12
+ :fixed,
13
+ :default,
14
+ :max_occurs,
15
+ :min_occurs
16
+
17
+ def initialize(name: nil, ref: nil)
18
+ @name = name
19
+ @ref = ref
20
+ end
21
+
22
+ def to_attributes(indent)
23
+ return if skippable?
24
+ return unless resolved_type
25
+
26
+ "#{indent}attribute :#{resolved_name}, :#{resolved_type}#{attribute_options}\n"
27
+ end
28
+
29
+ def to_xml_mapping(indent)
30
+ return if skippable?
31
+ return unless resolved_type
32
+
33
+ "#{indent}map_element :#{resolved_name(change_case: false)}, to: :#{resolved_name}#{render_default_option}\n"
34
+ end
35
+
36
+ def required_files
37
+ return if skippable?
38
+
39
+ element_type = resolved_type(change_case: false)
40
+ return "require \"bigdecimal\"" if element_type == "decimal"
41
+ return if SimpleType.skippable?(element_type)
42
+
43
+ "require_relative \"#{Utils.snake_case(element_type)}\""
44
+ end
45
+
46
+ private
47
+
48
+ def resolved_instance
49
+ @resolved_instance ||= XmlCompiler.instance_variable_get(:@elements)[last_of_split]
50
+ end
51
+
52
+ def resolved_type(change_case: true)
53
+ @current_type ||= type || resolved_instance&.type
54
+ klass_name = last_of_split(@current_type)
55
+ change_case ? Utils.snake_case(klass_name) : klass_name
56
+ end
57
+
58
+ def resolved_name(change_case: true)
59
+ @current_name ||= name || resolved_instance&.name
60
+ change_case ? Utils.snake_case(@current_name) : @current_name
61
+ end
62
+
63
+ def collection_option
64
+ return if min_occurs.nil? && max_occurs.nil?
65
+
66
+ min_value = min_occurs.nil? ? 1 : min_occurs.to_i
67
+ ", collection: #{min_value}..#{max_value}"
68
+ end
69
+
70
+ def max_value
71
+ return "Float::INFINITY" if max_occurs == "unbounded"
72
+ return 1 if max_occurs.nil?
73
+
74
+ max_occurs.to_i
75
+ end
76
+
77
+ def default_option
78
+ return if default.nil?
79
+
80
+ ", default: -> { #{default.inspect} }"
81
+ end
82
+
83
+ def render_default_option
84
+ return if default.nil?
85
+
86
+ ", render_default: true"
87
+ end
88
+
89
+ def attribute_options
90
+ [collection_option, default_option].compact.join
91
+ end
92
+
93
+ def last_of_split(field = ref)
94
+ field&.split(":")&.last
95
+ end
96
+
97
+ def skippable?
98
+ resolved_name == "schema_location"
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end