lutaml-model 0.8.17 → 0.8.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) 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/schema-import.adoc +42 -0
  14. data/docs/_guides/xml-mapping.adoc +21 -0
  15. data/docs/_pages/validation.adoc +65 -0
  16. data/lib/compat/opal/generate_boot.rb +123 -0
  17. data/lib/compat/opal/js_bundle_entry.rb +42 -0
  18. data/lib/compat/opal/lutaml_model_boot.rb +497 -0
  19. data/lib/compat/opal/moxml_boot.rb +68 -0
  20. data/lib/compat/opal/yaml_compat.rb +32 -0
  21. data/lib/lutaml/json.rb +1 -1
  22. data/lib/lutaml/key_value/transform.rb +3 -8
  23. data/lib/lutaml/key_value/transformation/value_serializer.rb +14 -1
  24. data/lib/lutaml/key_value/transformation.rb +17 -5
  25. data/lib/lutaml/model/adapter_resolver.rb +2 -2
  26. data/lib/lutaml/model/attribute.rb +88 -11
  27. data/lib/lutaml/model/cached_type_resolver.rb +1 -1
  28. data/lib/lutaml/model/error/union_schema_unsupported_error.rb +11 -0
  29. data/lib/lutaml/model/global_context.rb +11 -8
  30. data/lib/lutaml/model/instrumentation.rb +2 -2
  31. data/lib/lutaml/model/mapping/mapping_rule.rb +0 -6
  32. data/lib/lutaml/model/register.rb +2 -2
  33. data/lib/lutaml/model/runtime_compatibility.rb +50 -0
  34. data/lib/lutaml/model/schema/class_loader.rb +58 -0
  35. data/lib/lutaml/model/schema/compiled_output.rb +83 -0
  36. data/lib/lutaml/model/schema/definitions/attribute.rb +32 -0
  37. data/lib/lutaml/model/schema/definitions/choice.rb +20 -0
  38. data/lib/lutaml/model/schema/definitions/facet.rb +40 -0
  39. data/lib/lutaml/model/schema/definitions/group_import.rb +22 -0
  40. data/lib/lutaml/model/schema/definitions/member_walk.rb +33 -0
  41. data/lib/lutaml/model/schema/definitions/model.rb +40 -0
  42. data/lib/lutaml/model/schema/definitions/namespace.rb +23 -0
  43. data/lib/lutaml/model/schema/definitions/restricted_type.rb +30 -0
  44. data/lib/lutaml/model/schema/definitions/sequence.rb +19 -0
  45. data/lib/lutaml/model/schema/definitions/simple_content.rb +22 -0
  46. data/lib/lutaml/model/schema/definitions/transform_facet.rb +21 -0
  47. data/lib/lutaml/model/schema/definitions/type_ref.rb +20 -0
  48. data/lib/lutaml/model/schema/definitions/union_type.rb +28 -0
  49. data/lib/lutaml/model/schema/definitions/xml_root.rb +20 -0
  50. data/lib/lutaml/model/schema/definitions.rb +24 -0
  51. data/lib/lutaml/model/schema/file_writer.rb +70 -0
  52. data/lib/lutaml/model/schema/generator/definitions_collection.rb +16 -2
  53. data/lib/lutaml/model/schema/generator/property.rb +21 -4
  54. data/lib/lutaml/model/schema/module_nesting.rb +31 -0
  55. data/lib/lutaml/model/schema/namespace_naming.rb +46 -0
  56. data/lib/lutaml/model/schema/registry_generator.rb +115 -0
  57. data/lib/lutaml/model/schema/renderers/base.rb +40 -0
  58. data/lib/lutaml/model/schema/renderers/mappings.rb +72 -0
  59. data/lib/lutaml/model/schema/renderers/member_decls.rb +115 -0
  60. data/lib/lutaml/model/schema/renderers/model.rb +121 -0
  61. data/lib/lutaml/model/schema/renderers/namespace.rb +30 -0
  62. data/lib/lutaml/model/schema/renderers/registration.rb +64 -0
  63. data/lib/lutaml/model/schema/renderers/required_files_calculator.rb +93 -0
  64. data/lib/lutaml/model/schema/renderers/restricted_type.rb +88 -0
  65. data/lib/lutaml/model/schema/renderers/union.rb +83 -0
  66. data/lib/lutaml/model/schema/renderers.rb +19 -0
  67. data/lib/lutaml/model/schema/rnc_compiler/source_resolver.rb +47 -0
  68. data/lib/lutaml/model/schema/rnc_compiler.rb +62 -0
  69. data/lib/lutaml/model/schema/rng_compiler/define_classifier.rb +91 -0
  70. data/lib/lutaml/model/schema/rng_compiler/element_visitor.rb +392 -0
  71. data/lib/lutaml/model/schema/rng_compiler/member_collector.rb +30 -0
  72. data/lib/lutaml/model/schema/rng_compiler/rng_helpers.rb +152 -0
  73. data/lib/lutaml/model/schema/rng_compiler/value_type_resolver.rb +125 -0
  74. data/lib/lutaml/model/schema/rng_compiler.rb +220 -0
  75. data/lib/lutaml/model/schema/templates.rb +162 -0
  76. data/lib/lutaml/model/schema/xml_compiler/element_order.rb +41 -0
  77. data/lib/lutaml/model/schema/xml_compiler/registry_generator.rb +31 -107
  78. data/lib/lutaml/model/schema/xml_compiler/spec_builder/complex_types.rb +191 -0
  79. data/lib/lutaml/model/schema/xml_compiler/spec_builder/members.rb +216 -0
  80. data/lib/lutaml/model/schema/xml_compiler/spec_builder/simple_types.rb +153 -0
  81. data/lib/lutaml/model/schema/xml_compiler/spec_builder.rb +167 -0
  82. data/lib/lutaml/model/schema/xml_compiler/supported_data_types.rb +87 -0
  83. data/lib/lutaml/model/schema/xml_compiler.rb +38 -517
  84. data/lib/lutaml/model/schema.rb +19 -0
  85. data/lib/lutaml/model/serialize/attribute_definition.rb +14 -2
  86. data/lib/lutaml/model/serialize/builder.rb +15 -2
  87. data/lib/lutaml/model/serialize/value_mapping.rb +9 -57
  88. data/lib/lutaml/model/serialize.rb +6 -9
  89. data/lib/lutaml/model/services/rule_value_extractor.rb +2 -3
  90. data/lib/lutaml/model/services.rb +0 -2
  91. data/lib/lutaml/model/store.rb +2 -1
  92. data/lib/lutaml/model/toml.rb +1 -1
  93. data/lib/lutaml/model/transform.rb +17 -39
  94. data/lib/lutaml/model/transformation_registry.rb +1 -1
  95. data/lib/lutaml/model/type.rb +1 -0
  96. data/lib/lutaml/model/union.rb +320 -0
  97. data/lib/lutaml/model/utils.rb +6 -0
  98. data/lib/lutaml/model/validation.rb +1 -4
  99. data/lib/lutaml/model/version.rb +1 -1
  100. data/lib/lutaml/model.rb +18 -0
  101. data/lib/lutaml/toml.rb +1 -1
  102. data/lib/lutaml/xml/adapter.rb +1 -1
  103. data/lib/lutaml/xml/adapter_loader.rb +1 -1
  104. data/lib/lutaml/xml/builder/base.rb +1 -1
  105. data/lib/lutaml/xml/builder.rb +1 -1
  106. data/lib/lutaml/xml/error/schema_validation_error.rb +25 -0
  107. data/lib/lutaml/xml/model_transform.rb +5 -1
  108. data/lib/lutaml/xml/schema/relaxng_schema.rb +5 -0
  109. data/lib/lutaml/xml/schema/xsd_schema.rb +4 -0
  110. data/lib/lutaml/xml/serialization/format_conversion.rb +48 -0
  111. data/lib/lutaml/xml/serialization/instance_methods.rb +13 -0
  112. data/lib/lutaml/xml/transformation/element_builder.rb +21 -8
  113. data/lib/lutaml/xml/transformation/ordered_applier.rb +6 -0
  114. data/lib/lutaml/xml/xsd_validator.rb +66 -0
  115. data/lib/lutaml/xml.rb +53 -12
  116. data/lutaml-model.gemspec +4 -2
  117. data/spec/fixtures/xml/schema/rnc/address_book.rnc +15 -0
  118. data/spec/fixtures/xml/schema/rnc/book_features.rnc +18 -0
  119. data/spec/fixtures/xml/schema/rnc/includes/book.rnc +6 -0
  120. data/spec/fixtures/xml/schema/rnc/includes/circular_a.rnc +8 -0
  121. data/spec/fixtures/xml/schema/rnc/includes/circular_b.rnc +8 -0
  122. data/spec/fixtures/xml/schema/rnc/includes/library.rnc +8 -0
  123. data/spec/fixtures/xml/schema/rng/address_book.rng +16 -0
  124. data/spec/fixtures/xml/schema/rng/book.rng +27 -0
  125. data/spec/fixtures/xml/schema/rng/fixed_value.rng +11 -0
  126. data/spec/fixtures/xml/schema/rng/fragment_in_choice.rng +30 -0
  127. data/spec/fixtures/xml/schema/rng/full_name.rng +39 -0
  128. data/spec/fixtures/xml/schema/rng/integer_range.rng +32 -0
  129. data/spec/fixtures/xml/schema/rng/list_type.rng +12 -0
  130. data/spec/fixtures/xml/schema/rng/namespaced.rng +9 -0
  131. data/spec/fixtures/xml/schema/rng/paragraph.rng +13 -0
  132. data/spec/fixtures/xml/schema/rng/person.rng +23 -0
  133. data/spec/fixtures/xml/schema/rng/union.rng +14 -0
  134. data/spec/fixtures/xml/validate_xml_with_address.xsd +13 -0
  135. data/spec/fixtures/xml/validate_xml_with_contact.xsd +11 -0
  136. data/spec/fixtures/xml/validate_xml_with_person.xsd +11 -0
  137. data/spec/fixtures/xml/validate_xml_with_person_strict.xsd +17 -0
  138. data/spec/fixtures/xml/validate_xml_with_product.xsd +37 -0
  139. data/spec/lutaml/model/attribute_apply_value_map_spec.rb +134 -0
  140. data/spec/lutaml/model/attribute_spec.rb +169 -12
  141. data/spec/lutaml/model/boot_manifest_spec.rb +30 -0
  142. data/spec/lutaml/model/collection_spec.rb +18 -0
  143. data/spec/lutaml/model/mixed_content_spec.rb +171 -0
  144. data/spec/lutaml/model/opal_smoke_spec.rb +67 -2
  145. data/spec/lutaml/model/ordered_content_spec.rb +30 -0
  146. data/spec/lutaml/model/raw_element_spec.rb +125 -0
  147. data/spec/lutaml/model/rule_value_extractor_spec.rb +7 -14
  148. data/spec/lutaml/model/runtime_compatibility_spec.rb +25 -0
  149. data/spec/lutaml/model/schema/renderers/model_spec.rb +149 -0
  150. data/spec/lutaml/model/schema/renderers/namespace_spec.rb +36 -0
  151. data/spec/lutaml/model/schema/renderers/restricted_type_spec.rb +75 -0
  152. data/spec/lutaml/model/schema/renderers/union_spec.rb +58 -0
  153. data/spec/lutaml/model/transform_apply_value_map_spec.rb +64 -0
  154. data/spec/lutaml/model/union_attribute_spec.rb +658 -0
  155. data/spec/lutaml/model/union_spec.rb +257 -0
  156. data/spec/lutaml/model/utils_spec.rb +14 -0
  157. data/spec/lutaml/xml/opal_xml_spec.rb +22 -20
  158. data/spec/lutaml/xml/schema/compiler_spec.rb +21 -4
  159. data/spec/lutaml/xml/schema/rnc_compiler_spec.rb +404 -0
  160. data/spec/lutaml/xml/schema/rng_compiler_spec.rb +893 -0
  161. data/spec/lutaml/xml/validate_xml_with_constructs_spec.rb +222 -0
  162. data/spec/lutaml/xml/validate_xml_with_spec.rb +186 -0
  163. data/spec/spec_helper.rb +28 -11
  164. data/spec/support/opal.rb +5 -2
  165. metadata +97 -31
  166. data/lib/lutaml/model/schema/xml_compiler/attribute.rb +0 -106
  167. data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +0 -47
  168. data/lib/lutaml/model/schema/xml_compiler/choice.rb +0 -67
  169. data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +0 -27
  170. data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +0 -34
  171. data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +0 -173
  172. data/lib/lutaml/model/schema/xml_compiler/element.rb +0 -121
  173. data/lib/lutaml/model/schema/xml_compiler/group.rb +0 -113
  174. data/lib/lutaml/model/schema/xml_compiler/restriction.rb +0 -104
  175. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +0 -52
  176. data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +0 -40
  177. data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +0 -265
  178. data/lib/lutaml/model/schema/xml_compiler/xml_namespace_class.rb +0 -110
  179. data/lib/lutaml/model/services/default_value_resolver.rb +0 -60
  180. data/spec/lutaml/model/services/default_value_resolver_spec.rb +0 -162
  181. data/spec/lutaml/xml/schema/compiler/attribute_group_spec.rb +0 -71
  182. data/spec/lutaml/xml/schema/compiler/attribute_spec.rb +0 -67
  183. data/spec/lutaml/xml/schema/compiler/choice_spec.rb +0 -74
  184. data/spec/lutaml/xml/schema/compiler/complex_content_restriction_spec.rb +0 -60
  185. data/spec/lutaml/xml/schema/compiler/complex_content_spec.rb +0 -41
  186. data/spec/lutaml/xml/schema/compiler/complex_type_spec.rb +0 -202
  187. data/spec/lutaml/xml/schema/compiler/element_spec.rb +0 -68
  188. data/spec/lutaml/xml/schema/compiler/group_spec.rb +0 -90
  189. data/spec/lutaml/xml/schema/compiler/restriction_spec.rb +0 -77
  190. data/spec/lutaml/xml/schema/compiler/sequence_spec.rb +0 -65
  191. data/spec/lutaml/xml/schema/compiler/simple_content_spec.rb +0 -61
  192. data/spec/lutaml/xml/schema/compiler/simple_type_spec.rb +0 -184
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "erb"
4
3
  require "tmpdir"
5
-
6
- # XSD schema compilation support - now integrated into lutaml-model
4
+ require "uri"
7
5
 
8
6
  module Lutaml
9
7
  module Model
@@ -11,32 +9,10 @@ module Lutaml
11
9
  module XmlCompiler
12
10
  extend self
13
11
 
14
- # Autoload subdirectory classes
15
- autoload :ComplexContentRestriction,
16
- "#{__dir__}/xml_compiler/complex_content_restriction"
17
- autoload :AttributeGroup, "#{__dir__}/xml_compiler/attribute_group"
18
- autoload :ComplexContent, "#{__dir__}/xml_compiler/complex_content"
19
- autoload :SimpleContent, "#{__dir__}/xml_compiler/simple_content"
20
- autoload :ComplexType, "#{__dir__}/xml_compiler/complex_type"
21
- autoload :Restriction, "#{__dir__}/xml_compiler/restriction"
22
- autoload :SimpleType, "#{__dir__}/xml_compiler/simple_type"
23
- autoload :Attribute, "#{__dir__}/xml_compiler/attribute"
24
- autoload :Sequence, "#{__dir__}/xml_compiler/sequence"
25
- autoload :Element, "#{__dir__}/xml_compiler/element"
26
- autoload :Choice, "#{__dir__}/xml_compiler/choice"
27
- autoload :Group, "#{__dir__}/xml_compiler/group"
28
- autoload :XmlNamespaceClass,
29
- "#{__dir__}/xml_compiler/xml_namespace_class"
30
- autoload :RegistryGenerator,
31
- "#{__dir__}/xml_compiler/registry_generator"
32
-
33
- attr_reader :simple_types,
34
- :group_types,
35
- :complex_types,
36
- :elements,
37
- :attributes,
38
- :attribute_groups,
39
- :namespace_classes
12
+ autoload :SpecBuilder, "#{__dir__}/xml_compiler/spec_builder"
13
+ autoload :RegistryGenerator, "#{__dir__}/xml_compiler/registry_generator"
14
+ autoload :SupportedDataTypes, "#{__dir__}/xml_compiler/supported_data_types"
15
+ autoload :ElementOrder, "#{__dir__}/xml_compiler/element_order"
40
16
 
41
17
  ELEMENT_ORDER_IGNORABLE = %w[import include].freeze
42
18
 
@@ -48,9 +24,6 @@ module Lutaml
48
24
  Lutaml::Model::Config.xml_adapter = :nokogiri
49
25
  MSG
50
26
 
51
- # NOTE: These must be full class names (strings), not symbols like :xml_id.
52
- # The type resolver looks up these strings directly in the type registry,
53
- # and symbols would be interpreted as literal type names rather than W3C types.
54
27
  XML_DEFINED_ATTRIBUTES = {
55
28
  "id" => "Lutaml::Xml::W3c::XmlIdType",
56
29
  "lang" => "Lutaml::Xml::W3c::XmlLangType",
@@ -59,522 +32,70 @@ module Lutaml
59
32
  }.freeze
60
33
  XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace"
61
34
 
35
+ # The SpecBuilder instance set up by `as_models`. Exposed so
36
+ # tests can introspect the walked Definitions::* hashes (e.g.
37
+ # `XmlCompiler.builder.attributes["id"]`) without re-walking.
38
+ attr_reader :builder
39
+
62
40
  def to_models(schema, options = {})
63
41
  as_models(schema, options: options)
64
42
  options[:indent] = options[:indent] ? options[:indent].to_i : 2
65
43
 
66
- # Auto-generate module namespace from output directory if not explicitly set
67
44
  unless options.key?(:module_namespace)
68
45
  output_dir = options.fetch(:output_dir,
69
46
  "lutaml_models_#{Time.now.to_i}")
70
- # Generate a namespace from the directory name (e.g., "my_models" -> "MyModels")
71
- dir_name = File.basename(output_dir).split("_").map(&:capitalize).join
72
- options[:module_namespace] = dir_name
47
+ options[:module_namespace] =
48
+ File.basename(output_dir).split("_").map(&:capitalize).join
73
49
  end
74
50
 
75
- # Allow explicit nil to disable namespace
76
- # only set default register_id if module_namespace is present
77
- if options[:module_namespace]
78
- options[:register_id] ||= :default
79
- end
51
+ options[:register_id] ||= :default if options[:module_namespace]
80
52
 
81
- @simple_types.merge!(XmlCompiler::SimpleType.setup_supported_types)
53
+ @builder.add_supported_types
54
+ @builder.finalize_required_files!
82
55
 
83
- # Generate namespace classes
84
- namespace_classes_hash = {}
85
- @namespace_classes.each do |name, ns_class|
86
- namespace_classes_hash[name] = ns_class.to_class(options: options)
87
- end
88
-
89
- classes_list = namespace_classes_hash.merge(@simple_types).merge(@complex_types).merge(@group_types)
90
- classes_list = classes_list.transform_values do |type|
91
- # Skip namespace classes (already strings) and only process model types
92
- next type if type.is_a?(String)
56
+ output = build_output(options)
93
57
 
94
- type.to_class(options: options.merge(
95
- module_namespace: options[:module_namespace],
96
- register_id: options[:register_id],
97
- ))
98
- end
99
58
  if options[:create_files]
100
59
  dir = options.fetch(:output_dir, "lutaml_models_#{Time.now.to_i}")
101
-
102
- # If module_namespace provided, create subdirectories
103
- if options[:module_namespace]
104
- module_path = options[:module_namespace].split("::").map(&:downcase).join("/")
105
- full_dir = File.join(dir, module_path)
106
- FileUtils.mkdir_p(full_dir)
107
-
108
- # Generate central registry file with autoload
109
- registry_content = RegistryGenerator.generate(classes_list,
110
- options)
111
- if registry_content
112
- # Registry file goes in parent directory of module path
113
- registry_name = module_path.split("/").last
114
- registry_file = File.join(dir, "#{registry_name}_registry.rb")
115
- File.write(registry_file, registry_content)
116
- end
117
-
118
- # Write class files
119
- classes_list.each do |name, klass|
120
- create_file(name, klass, full_dir)
121
- end
122
- else
123
- # When no module_namespace, write files directly to output dir
124
- # Generated files use require_relative for dependencies (traditional approach)
125
- FileUtils.mkdir_p(dir)
126
-
127
- # Write class files
128
- classes_list.each do |name, klass|
129
- create_file(name, klass, dir)
130
- end
131
- end
60
+ FileWriter.write(output, dir, registry_generator: RegistryGenerator)
132
61
  true
133
62
  else
134
- require_classes(classes_list) if options[:load_classes]
135
- classes_list
136
- end
137
- end
138
-
139
- def create_file(name, content, dir)
140
- name = name.split(":").last
141
- File.write("#{dir}/#{Utils.snake_case(name)}.rb", content)
142
- end
143
-
144
- def require_classes(classes_hash)
145
- Dir.mktmpdir do |dir|
146
- # Create subdirectory for class files (matches autoload path in registry)
147
- module_subdir = "generatedmodels"
148
- full_dir = File.join(dir, module_subdir)
149
- FileUtils.mkdir_p(full_dir)
150
-
151
- # Generate registry file first with autoload
152
- registry_content = RegistryGenerator.generate(classes_hash,
153
- module_namespace: "GeneratedModels",
154
- register_id: :default)
155
- if registry_content
156
- registry_file = File.join(dir, "registry.rb")
157
- File.write(registry_file, registry_content)
158
- end
159
-
160
- # Write class files to subdirectory
161
- classes_hash.each do |name, klass|
162
- create_file(name, klass, full_dir)
163
- end
164
-
165
- # Require the registry first to set up autoloads
166
- require "#{dir}/registry"
167
-
168
- # Call register_all to register all classes
169
- GeneratedModels.register_all if defined?(GeneratedModels)
63
+ ClassLoader.load(output, registry_generator: RegistryGenerator) if options[:load_classes]
64
+ output.sources
170
65
  end
171
66
  end
172
67
 
173
68
  def as_models(schema, options: {})
174
69
  unless Config.xml_adapter.name.end_with?("NokogiriAdapter")
175
- raise Error,
176
- XML_ADAPTER_NOT_SET_MESSAGE
70
+ raise Error, XML_ADAPTER_NOT_SET_MESSAGE
177
71
  end
178
72
 
179
- parsed_schema = Lutaml::Xml::Schema::Xsd.parse(schema,
180
- location: options[:location])
181
-
182
- @elements = MappingHash.new
183
- @attributes = MappingHash.new
184
- @group_types = MappingHash.new
185
- @simple_types = MappingHash.new
186
- @complex_types = MappingHash.new
187
- @attribute_groups = MappingHash.new
188
- @namespace_classes = MappingHash.new
189
-
190
- populate_default_values
191
- collect_namespaces(Array(parsed_schema), options)
192
- schema_to_models(Array(parsed_schema))
193
- end
194
-
195
- def populate_default_values
196
- XML_DEFINED_ATTRIBUTES.each do |name, value|
197
- @attributes[name] = Attribute.new(name: name)
198
- @attributes[name].type = value
199
- end
200
-
201
- # W3C XmlNamespace is now auto-loaded via lutaml/model.rb
202
- # Reference: Lutaml::Xml::W3c::XmlNamespace
203
- end
204
-
205
- def schema_to_models(schemas)
206
- return if schemas.empty?
207
-
208
- schemas.each do |schema|
209
- schema_to_models(schema.include) if schema.include&.any?
210
- schema_to_models(schema.import) if schema.import&.any?
211
- # Use schema's resolved_element_order which returns properly typed XSD objects
212
- schema.resolved_element_order.each do |order_item|
213
- item_name = order_item.name if order_item.is_a?(Lutaml::Xml::Schema::Xsd::SimpleType) ||
214
- order_item.is_a?(Lutaml::Xml::Schema::Xsd::Group) ||
215
- order_item.is_a?(Lutaml::Xml::Schema::Xsd::ComplexType) ||
216
- order_item.is_a?(Lutaml::Xml::Schema::Xsd::Element) ||
217
- order_item.is_a?(Lutaml::Xml::Schema::Xsd::Attribute) ||
218
- order_item.is_a?(Lutaml::Xml::Schema::Xsd::AttributeGroup)
219
- case order_item
220
- when Lutaml::Xml::Schema::Xsd::SimpleType
221
- @simple_types[item_name] = setup_simple_type(order_item)
222
- when Lutaml::Xml::Schema::Xsd::Group
223
- @group_types[item_name] =
224
- setup_group_type(order_item, root_call: true)
225
- when Lutaml::Xml::Schema::Xsd::ComplexType
226
- @complex_types[item_name] = setup_complex_type(order_item)
227
- when Lutaml::Xml::Schema::Xsd::Element
228
- @elements[item_name] = setup_element(order_item)
229
- when Lutaml::Xml::Schema::Xsd::Attribute
230
- next if xml_defined_attribute?(schema, item_name)
231
-
232
- @attributes[item_name] = setup_attribute(order_item)
233
- when Lutaml::Xml::Schema::Xsd::AttributeGroup
234
- @attribute_groups[item_name] =
235
- setup_attribute_groups(order_item)
236
- end
237
- end
238
- end
239
- nil
240
- end
241
-
242
- def setup_simple_type(simple_type)
243
- SimpleType.new(simple_type.name).tap do |type_object|
244
- if union = simple_type.union
245
- type_object.unions = union.member_types.split
246
- elsif restriction = simple_type.restriction
247
- type_object.base_class = restriction.base&.split(":")&.last
248
- type_object.instance = setup_restriction(restriction)
249
- end
250
- end
251
- end
252
-
253
- def restriction_content(instance, restriction)
254
- restriction_min_max(restriction, instance, field: :max_length,
255
- value_method: :min)
256
- restriction_min_max(restriction, instance, field: :min_length,
257
- value_method: :max)
258
- restriction_min_max(restriction, instance, field: :min_inclusive,
259
- value_method: :max)
260
- restriction_min_max(restriction, instance, field: :max_inclusive,
261
- value_method: :min)
262
- restriction_min_max(restriction, instance, field: :max_exclusive,
263
- value_method: :max)
264
- restriction_min_max(restriction, instance, field: :min_exclusive,
265
- value_method: :min)
266
- instance.length = restriction_length(restriction.length) if restriction.length&.any?
267
- end
268
-
269
- # Use min/max to get the value from the field_value array.
270
- def restriction_min_max(restriction, instance, field:,
271
- value_method: :min)
272
- field_value = restriction.public_send(field)
273
- return unless field_value&.any?
274
-
275
- instance.public_send(
276
- :"#{field}=",
277
- field_value.map(&:value).public_send(value_method).to_s,
73
+ parsed_schema = Lutaml::Xml::Schema::Xsd.parse(
74
+ schema, location: options[:location]
278
75
  )
279
- end
280
-
281
- def restriction_length(lengths)
282
- lengths.map do |length|
283
- MappingHash.new.tap do |hash|
284
- hash[:value] = length.value
285
- hash[:fixed] = length.fixed if length.fixed
286
- end
287
- end
288
- end
289
-
290
- def setup_complex_type(complex_type)
291
- ComplexType.new.tap do |instance|
292
- instance.id = complex_type.id
293
- instance.name = complex_type.name
294
- instance.mixed = complex_type.mixed
295
- resolved_element_order(complex_type).each do |element|
296
- case element
297
- when Lutaml::Xml::Schema::Xsd::Attribute
298
- instance << setup_attribute(element)
299
- when Lutaml::Xml::Schema::Xsd::Sequence
300
- instance << setup_sequence(element)
301
- when Lutaml::Xml::Schema::Xsd::Choice
302
- instance << setup_choice(element)
303
- when Lutaml::Xml::Schema::Xsd::ComplexContent
304
- instance << setup_complex_content(element, instance.name,
305
- instance)
306
- when Lutaml::Xml::Schema::Xsd::AttributeGroup
307
- instance << setup_attribute_groups(element)
308
- when Lutaml::Xml::Schema::Xsd::Group
309
- instance << setup_group_type(element)
310
- when Lutaml::Xml::Schema::Xsd::SimpleContent
311
- instance.simple_content = setup_simple_content(element)
312
- end
313
- end
314
- end
315
- end
316
-
317
- def setup_simple_content(simple_content)
318
- SimpleContent.new.tap do |instance|
319
- if simple_content.extension
320
- instance.base_class = simple_content.extension.base
321
- setup_extension(simple_content.extension, instance)
322
- elsif simple_content.restriction
323
- instance.base_class = simple_content.restriction.base
324
- instance << setup_restriction(simple_content.restriction)
325
- end
326
- end
327
- end
328
-
329
- def setup_sequence(sequence)
330
- Sequence.new.tap do |instance|
331
- resolved_element_order(sequence).each do |object|
332
- # No implementation yet for Lutaml::Xml::Schema::Xsd::Any!
333
- next if object.is_a?(Lutaml::Xml::Schema::Xsd::Any)
334
-
335
- instance << case object
336
- when Lutaml::Xml::Schema::Xsd::Sequence
337
- setup_sequence(object)
338
- when Lutaml::Xml::Schema::Xsd::Element
339
- setup_element(object)
340
- when Lutaml::Xml::Schema::Xsd::Choice
341
- setup_choice(object)
342
- when Lutaml::Xml::Schema::Xsd::Group
343
- setup_group_type(object)
344
- end
345
- end
346
- end
347
- end
76
+ schemas = Array(parsed_schema)
348
77
 
349
- def setup_group_type(group, root_call: false)
350
- object = Group.new(group.name, group.ref)
351
- object.instance = setup_group_type_instance(group)
352
- @group_types[group.name] = object if group.name && !root_call
353
- object
78
+ @builder = SpecBuilder.new
79
+ @builder.populate_default_attributes
80
+ @builder.collect_namespaces(schemas, options)
81
+ @builder.walk_schemas(schemas)
354
82
  end
355
83
 
356
- def setup_group_type_instance(group)
357
- if sequence = group.sequence
358
- setup_sequence(sequence)
359
- elsif choice = group.choice
360
- setup_choice(choice)
361
- end
362
- end
363
-
364
- def setup_choice(choice)
365
- Choice.new.tap do |instance|
366
- instance.min_occurs = choice.min_occurs
367
- instance.max_occurs = choice.max_occurs
368
- resolved_element_order(choice).each do |element|
369
- instance << case element
370
- when Lutaml::Xml::Schema::Xsd::Element
371
- setup_element(element)
372
- when Lutaml::Xml::Schema::Xsd::Sequence
373
- setup_sequence(element)
374
- when Lutaml::Xml::Schema::Xsd::Group
375
- setup_group_type(element)
376
- when Lutaml::Xml::Schema::Xsd::Choice
377
- setup_choice(element)
378
- end
379
- end
380
- end
381
- end
382
-
383
- def setup_attribute(attribute)
384
- instance = Attribute.new(name: attribute.name, ref: attribute.ref)
385
- if attribute.name
386
- instance.type = setup_attribute_type(attribute)
387
- instance.default = attribute.default
388
- end
389
- instance
390
- end
391
-
392
- def setup_attribute_type(attribute)
393
- return attribute.type if attribute.type
394
-
395
- simple_type = attribute.simple_type
396
- attr_name = "ST_#{attribute.name}"
397
- simple_type.name = attr_name
398
- @simple_types[attr_name] = setup_simple_type(simple_type)
399
- attr_name
400
- end
84
+ private
401
85
 
402
- def setup_attribute_groups(attribute_group)
403
- instance = AttributeGroup.new(name: attribute_group.name,
404
- ref: attribute_group.ref)
405
- if attribute_group.name
406
- resolved_element_order(attribute_group).each do |object|
407
- group_attribute = case object
408
- when Lutaml::Xml::Schema::Xsd::Attribute
409
- setup_attribute(object)
410
- when Lutaml::Xml::Schema::Xsd::AttributeGroup
411
- setup_attribute_groups(object)
412
- end
413
- instance << group_attribute if group_attribute
414
- end
86
+ def build_output(options)
87
+ model_entries = @builder.all_models.map do |name, spec|
88
+ CompiledOutput::Entry.new(name, spec, :model)
415
89
  end
416
- instance
417
- end
418
-
419
- def setup_element(element)
420
- element_name = element.name
421
- instance = Element.new(name: element_name, ref: element.ref)
422
- instance.min_occurs = element.min_occurs
423
- instance.max_occurs = element.max_occurs
424
- if element_name
425
- instance.type = setup_element_type(element, instance)
426
- instance.id = element.id
427
- instance.fixed = element.fixed
428
- instance.default = element.default
90
+ namespace_entries = @builder.namespace_classes.map do |name, spec|
91
+ CompiledOutput::Entry.new(name, spec, :namespace)
429
92
  end
430
- instance
431
- end
432
-
433
- # Populates @simple_types or @complex_types based on elements available value.
434
- def setup_element_type(element, _instance)
435
- return element.type if element.type
436
-
437
- type, prefix = if element.simple_type
438
- ["simple",
439
- "ST"]
440
- else
441
- ["complex", "CT"]
442
- end
443
- type_instance = element.public_send(:"#{type}_type")
444
- type_instance.name = [prefix, element.name].join("_")
445
- instance_variable_get(:"@#{type}_types")[type_instance.name] =
446
- public_send(:"setup_#{type}_type", type_instance)
447
- type_instance.name
448
- end
449
-
450
- def setup_restriction(restriction)
451
- Restriction.new.tap do |instance|
452
- instance.base_class = restriction.base
453
- restriction_patterns(restriction.pattern,
454
- instance)
455
- restriction_content(instance, restriction)
456
- if restriction.enumeration&.any?
457
- instance.enumerations = restriction.enumeration.map(&:value)
458
- end
459
- end
460
- end
461
-
462
- def setup_complex_content_restriction(restriction,
463
- compiler_complex_type)
464
- ComplexContentRestriction.new.tap do |instance|
465
- compiler_complex_type.base_class = restriction.base
466
- resolved_element_order(restriction).each do |element|
467
- # For restrictions, only add attributes and attribute groups.
468
- # Sequence/choice/group elements are inherited from the base class
469
- # and adding them here causes duplicate mappings.
470
- next if element.is_a?(Lutaml::Xml::Schema::Xsd::Sequence) || element.is_a?(Lutaml::Xml::Schema::Xsd::Choice) || element.is_a?(Lutaml::Xml::Schema::Xsd::Group)
471
-
472
- instance << case element
473
- when Lutaml::Xml::Schema::Xsd::Attribute
474
- setup_attribute(element)
475
- when Lutaml::Xml::Schema::Xsd::AttributeGroup
476
- setup_attribute_groups(element)
477
- when Lutaml::Xml::Schema::Xsd::Sequence
478
- setup_sequence(element)
479
- when Lutaml::Xml::Schema::Xsd::Choice
480
- setup_choice(element)
481
- when Lutaml::Xml::Schema::Xsd::Group
482
- setup_group_type(element)
483
- end
484
- end
485
- end
486
- end
487
-
488
- def restriction_patterns(patterns, instance)
489
- return if Utils.blank?(patterns)
490
-
491
- instance.pattern = patterns.map { |p| "(#{p.value})" }.join("|")
492
- end
493
93
 
494
- def setup_complex_content(complex_content, name,
495
- compiler_complex_type)
496
- @complex_types[name] = ComplexContent.new.tap do |instance|
497
- compiler_complex_type.mixed = complex_content.mixed
498
- if extension = complex_content.extension
499
- setup_extension(extension, compiler_complex_type)
500
- elsif restriction = complex_content.restriction
501
- instance.restriction = setup_complex_content_restriction(restriction, compiler_complex_type)
502
- end
503
- end
504
- end
505
-
506
- def setup_extension(extension, instance)
507
- instance.base_class = extension.base
508
- resolved_element_order(extension).each do |element|
509
- instance << case element
510
- when Lutaml::Xml::Schema::Xsd::Attribute
511
- setup_attribute(element)
512
- when Lutaml::Xml::Schema::Xsd::AttributeGroup
513
- setup_attribute_groups(element)
514
- when Lutaml::Xml::Schema::Xsd::Sequence
515
- setup_sequence(element)
516
- when Lutaml::Xml::Schema::Xsd::Choice
517
- setup_choice(element)
518
- when Lutaml::Xml::Schema::Xsd::Group
519
- setup_group_type(element)
520
- end
521
- end
522
- end
523
-
524
- def resolved_element_order(object)
525
- return [] if object.element_order.nil?
526
-
527
- # If the object is an XSD type (which has its own resolved_element_order),
528
- # use it instead of processing element_order which returns generic XML elements
529
- if object.is_a?(Lutaml::Xml::Schema::Xsd::Base)
530
- return object.resolved_element_order
531
- end
532
-
533
- object.element_order.each_with_object(object.element_order.dup) do |builder_instance, array|
534
- next array.delete(builder_instance) if builder_instance.text? || ELEMENT_ORDER_IGNORABLE.include?(builder_instance.name)
535
-
536
- index = 0
537
- array.each_with_index do |element, i|
538
- next unless element == builder_instance
539
-
540
- array[i] =
541
- Array(object.public_send(Utils.snake_case(builder_instance.name)))[index]
542
- index += 1
543
- end
544
- end
545
-
546
- object.element_order
547
- end
548
-
549
- def collect_namespaces(schemas, options)
550
- # Collect unique namespace URIs from the schemas
551
- namespace_uris = Set.new
552
-
553
- # Add the main namespace from options if provided
554
- if options[:namespace]
555
- namespace_uris.add(options[:namespace])
556
- end
557
-
558
- # Extract namespaces from schema elements
559
- schemas.each do |schema|
560
- namespace_uris.add(schema.target_namespace) if schema.target_namespace
561
- end
562
-
563
- # Create XmlNamespaceClass for each unique namespace
564
- namespace_uris.each do |uri|
565
- next if uri.nil? || uri.empty?
566
-
567
- # Use provided prefix if available
568
- prefix = options[:prefix] if options[:namespace] == uri
569
-
570
- ns_class = XmlNamespaceClass.new(uri: uri, prefix: prefix)
571
- @namespace_classes[ns_class.class_name] = ns_class
572
- end
573
- end
574
-
575
- def xml_defined_attribute?(schema, item_name)
576
- schema.target_namespace == XML_NAMESPACE_URI &&
577
- XML_DEFINED_ATTRIBUTES.key?(item_name)
94
+ CompiledOutput.new(
95
+ entries: model_entries + namespace_entries,
96
+ module_namespace: options[:module_namespace],
97
+ register_id: options[:register_id] || :default,
98
+ )
578
99
  end
579
100
  end
580
101
  end
@@ -4,6 +4,13 @@ module Lutaml
4
4
  module Model
5
5
  module Schema
6
6
  autoload :BaseSchema, "#{__dir__}/schema/base_schema"
7
+ autoload :ClassLoader, "#{__dir__}/schema/class_loader"
8
+ autoload :CompiledOutput, "#{__dir__}/schema/compiled_output"
9
+ autoload :FileWriter, "#{__dir__}/schema/file_writer"
10
+ autoload :ModuleNesting, "#{__dir__}/schema/module_nesting"
11
+ autoload :NamespaceNaming, "#{__dir__}/schema/namespace_naming"
12
+ autoload :RegistryGenerator, "#{__dir__}/schema/registry_generator"
13
+ autoload :Templates, "#{__dir__}/schema/templates"
7
14
  autoload :SharedMethods, "#{__dir__}/schema/shared_methods"
8
15
  autoload :Helpers, "#{__dir__}/schema/helpers"
9
16
  autoload :JsonSchema, "#{__dir__}/schema/json_schema"
@@ -12,11 +19,15 @@ module Lutaml
12
19
  self,
13
20
  {
14
21
  XmlCompiler: "#{__dir__}/schema/xml_compiler",
22
+ RngCompiler: "#{__dir__}/schema/rng_compiler",
23
+ RncCompiler: "#{__dir__}/schema/rnc_compiler",
15
24
  },
16
25
  )
17
26
  autoload :Generator, "#{__dir__}/schema/generator"
18
27
  autoload :Renderer, "#{__dir__}/schema/renderer"
19
28
  autoload :Decorators, "#{__dir__}/schema/decorators"
29
+ autoload :Definitions, "#{__dir__}/schema/definitions"
30
+ autoload :Renderers, "#{__dir__}/schema/renderers"
20
31
 
21
32
  # Registry for format-specific schema methods.
22
33
  # Format plugins register their schema methods at load time.
@@ -62,6 +73,14 @@ module Lutaml
62
73
  def self.from_xml(_xml, _options = {})
63
74
  raise "XML schema compilation requires lutaml-xml. Add it to your Gemfile."
64
75
  end
76
+
77
+ def self.from_relaxng(_rng, _options = {})
78
+ raise "RELAX NG schema compilation requires lutaml-xml. Add it to your Gemfile."
79
+ end
80
+
81
+ def self.from_rnc(_rnc, _options = {})
82
+ raise "RNC schema compilation requires lutaml-xml. Add it to your Gemfile."
83
+ end
65
84
  end
66
85
  end
67
86
  end
@@ -131,8 +131,15 @@ module Lutaml
131
131
  end
132
132
 
133
133
  enum_shorthand_names = instance_variable_get(:@__enum_shorthand_names__) || Set.new
134
- unless method_defined?(:"#{name}=",
135
- false) && !enum_shorthand_names.include?(name.to_s)
134
+ # Opal's Module#method_defined? only accepts 1 arg (MRI accepts
135
+ # the optional `inherit` flag). Skip the flag there — the
136
+ # difference only matters for inherited-method filtering.
137
+ setter_defined = if Lutaml::Model.opal?
138
+ method_defined?(:"#{name}=")
139
+ else
140
+ method_defined?(:"#{name}=", false)
141
+ end
142
+ unless setter_defined && !enum_shorthand_names.include?(name.to_s)
136
143
  if attr.collection?
137
144
  define_method(:"#{name}=") do |value|
138
145
  value_set_for(name)
@@ -169,6 +176,11 @@ module Lutaml
169
176
  def attribute(name, type, options = {})
170
177
  type, options = process_type_hash(type, options) if type.is_a?(::Hash)
171
178
 
179
+ if type.is_a?(::Array)
180
+ options = options.merge(union_member_types: type)
181
+ type = Lutaml::Model::Type::Union
182
+ end
183
+
172
184
  # Handle direct method option in options hash
173
185
  if options[:method]
174
186
  options[:method_name] = options.delete(:method)