Module: Lutaml::Model::Schema::XmlCompiler

Extended by:
XmlCompiler
Included in:
XmlCompiler
Defined in:
lib/lutaml/model/schema/xml_compiler.rb,
lib/lutaml/model/schema/xml_compiler/group.rb,
lib/lutaml/model/schema/xml_compiler/choice.rb,
lib/lutaml/model/schema/xml_compiler/element.rb,
lib/lutaml/model/schema/xml_compiler/sequence.rb,
lib/lutaml/model/schema/xml_compiler/attribute.rb,
lib/lutaml/model/schema/xml_compiler/restriction.rb,
lib/lutaml/model/schema/xml_compiler/simple_type.rb,
lib/lutaml/model/schema/xml_compiler/complex_type.rb,
lib/lutaml/model/schema/xml_compiler/simple_content.rb,
lib/lutaml/model/schema/xml_compiler/attribute_group.rb,
lib/lutaml/model/schema/xml_compiler/complex_content.rb,
lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb

Defined Under Namespace

Classes: Attribute, AttributeGroup, Choice, ComplexContent, ComplexContentRestriction, ComplexType, Element, Group, Restriction, Sequence, SimpleContent, SimpleType

Constant Summary collapse

ELEMENT_ORDER_IGNORABLE =
%w[import include].freeze
XML_ADAPTER_NOT_SET_MESSAGE =
"Nokogiri is not set as XML Adapter.\nMake sure Nokogiri is installed and set as XML Adapter eg.\nexecute: gem install nokogiri\nrequire 'lutaml/model/adapter/nokogiri'\nLutaml::Model.xml_adapter = Lutaml::Model::Adapter::Nokogiri\n"
XML_DEFINED_ATTRIBUTES =
{
  "id" => "id",
  "lang" => "language",
  "space" => "NCName",
  "base" => "anyURI",
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#attribute_groupsObject (readonly)

Returns the value of attribute attribute_groups.



25
26
27
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 25

def attribute_groups
  @attribute_groups
end

#attributesObject (readonly)

Returns the value of attribute attributes.



25
26
27
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 25

def attributes
  @attributes
end

#complex_typesObject (readonly)

Returns the value of attribute complex_types.



25
26
27
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 25

def complex_types
  @complex_types
end

#elementsObject (readonly)

Returns the value of attribute elements.



25
26
27
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 25

def elements
  @elements
end

#group_typesObject (readonly)

Returns the value of attribute group_types.



25
26
27
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 25

def group_types
  @group_types
end

#simple_typesObject (readonly)

Returns the value of attribute simple_types.



25
26
27
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 25

def simple_types
  @simple_types
end

Instance Method Details

#as_models(schema, options: {}) ⇒ Object

Raises:



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 79

def as_models(schema, options: {})
  raise Error, XML_ADAPTER_NOT_SET_MESSAGE unless Config.xml_adapter.name.end_with?("NokogiriAdapter")

  parsed_schema = Xsd.parse(schema, location: options[:location])

  @elements = MappingHash.new
  @attributes = MappingHash.new
  @group_types = MappingHash.new
  @simple_types = MappingHash.new
  @complex_types = MappingHash.new
  @attribute_groups = MappingHash.new

  populate_default_values
  schema_to_models(Array(parsed_schema))
end

#create_file(name, content, dir) ⇒ Object



66
67
68
69
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 66

def create_file(name, content, dir)
  name = name.split(":").last
  File.write("#{dir}/#{Utils.snake_case(name)}.rb", content)
end

#populate_default_valuesObject



95
96
97
98
99
100
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 95

def populate_default_values
  XML_DEFINED_ATTRIBUTES.each do |name, value|
    @attributes[name] = Attribute.new(name: name)
    @attributes[name].type = value
  end
end

#require_classes(classes_hash) ⇒ Object



71
72
73
74
75
76
77
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 71

def require_classes(classes_hash)
  Dir.mktmpdir do |dir|
    classes_hash.each { |name, klass| create_file(name, klass, dir) }
    # Some files are not created at the time of the require, so we need to require them after all the files are created.
    classes_hash.each_key { |name| require "#{dir}/#{Utils.snake_case(name)}" }
  end
end

#resolved_element_order(object) ⇒ Object



390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 390

def resolved_element_order(object)
  return [] if object.element_order.nil?

  object.element_order.each_with_object(object.element_order.dup) do |builder_instance, array|
    next array.delete(builder_instance) if builder_instance.text? || ELEMENT_ORDER_IGNORABLE.include?(builder_instance.name)

    index = 0
    array.each_with_index do |element, i|
      next unless element == builder_instance

      array[i] = Array(object.send(Utils.snake_case(builder_instance.name)))[index]
      index += 1
    end
  end
end

#restriction_content(instance, restriction) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 140

def restriction_content(instance, restriction)
  return instance unless restriction.respond_to?(:max_length)

  restriction_min_max(restriction, instance, field: :max_length, value_method: :min)
  restriction_min_max(restriction, instance, field: :min_length, value_method: :max)
  restriction_min_max(restriction, instance, field: :min_inclusive, value_method: :max)
  restriction_min_max(restriction, instance, field: :max_inclusive, value_method: :min)
  restriction_min_max(restriction, instance, field: :max_exclusive, value_method: :max)
  restriction_min_max(restriction, instance, field: :min_exclusive, value_method: :min)
  instance.length = restriction_length(restriction.length) if restriction.length&.any?
end

#restriction_length(lengths) ⇒ Object



163
164
165
166
167
168
169
170
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 163

def restriction_length(lengths)
  lengths.map do |length|
    MappingHash.new.tap do |hash|
      hash[:value] = length.value
      hash[:fixed] = length.fixed if length.fixed
    end
  end
end

#restriction_min_max(restriction, instance, field:, value_method: :min) ⇒ Object

Use min/max to get the value from the field_value array.



153
154
155
156
157
158
159
160
161
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 153

def restriction_min_max(restriction, instance, field:, value_method: :min)
  field_value = restriction.public_send(field)
  return unless field_value&.any?

  instance.public_send(
    :"#{field}=",
    field_value.map(&:value).send(value_method).to_s,
  )
end

#restriction_patterns(patterns, instance) ⇒ Object



355
356
357
358
359
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 355

def restriction_patterns(patterns, instance)
  return if Utils.blank?(patterns)

  instance.pattern = patterns.map { |p| "(#{p.value})" }.join("|")
end

#schema_to_models(schemas) ⇒ Object



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 102

def schema_to_models(schemas)
  return if schemas.empty?

  schemas.each do |schema|
    schema_to_models(schema.include) if schema.include&.any?
    schema_to_models(schema.import) if schema.import&.any?
    resolved_element_order(schema).each do |order_item|
      item_name = order_item.name if order_item.respond_to?(:name)
      case order_item
      when Xsd::SimpleType
        @simple_types[item_name] = setup_simple_type(order_item)
      when Xsd::Group
        @group_types[item_name] = setup_group_type(order_item, root_call: true)
      when Xsd::ComplexType
        @complex_types[item_name] = setup_complex_type(order_item)
      when Xsd::Element
        @elements[item_name] = setup_element(order_item)
      when Xsd::Attribute
        @attributes[item_name] = setup_attribute(order_item)
      when Xsd::AttributeGroup
        @attribute_groups[item_name] = setup_attribute_groups(order_item)
      end
    end
  end
  nil
end

#setup_attribute(attribute) ⇒ Object



264
265
266
267
268
269
270
271
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 264

def setup_attribute(attribute)
  instance = Attribute.new(name: attribute.name, ref: attribute.ref)
  if attribute.name
    instance.type = setup_attribute_type(attribute)
    instance.default = attribute.default
  end
  instance
end

#setup_attribute_groups(attribute_group) ⇒ Object



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 283

def setup_attribute_groups(attribute_group)
  instance = AttributeGroup.new(name: attribute_group.name, ref: attribute_group.ref)
  if attribute_group.name
    resolved_element_order(attribute_group).each do |object|
      group_attribute = case object
                        when Xsd::Attribute
                          setup_attribute(object)
                        when Xsd::AttributeGroup
                          setup_attribute_groups(object)
                        end
      instance << group_attribute if group_attribute
    end
  end
  instance
end

#setup_attribute_type(attribute) ⇒ Object



273
274
275
276
277
278
279
280
281
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 273

def setup_attribute_type(attribute)
  return attribute.type if attribute.type

  simple_type = attribute.simple_type
  attr_name = "ST_#{attribute.name}"
  simple_type.name = attr_name
  @simple_types[attr_name] = setup_simple_type(simple_type)
  attr_name
end

#setup_choice(choice) ⇒ Object



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 245

def setup_choice(choice)
  Choice.new.tap do |instance|
    instance.min_occurs = choice.min_occurs
    instance.max_occurs = choice.max_occurs
    resolved_element_order(choice).each do |element|
      instance << case element
                  when Xsd::Element
                    setup_element(element)
                  when Xsd::Sequence
                    setup_sequence(element)
                  when Xsd::Group
                    setup_group_type(element)
                  when Xsd::Choice
                    setup_choice(element)
                  end
    end
  end
end

#setup_complex_content(complex_content, name, compiler_complex_type) ⇒ Object



361
362
363
364
365
366
367
368
369
370
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 361

def setup_complex_content(complex_content, name, compiler_complex_type)
  @complex_types[name] = ComplexContent.new.tap do |instance|
    compiler_complex_type.mixed = complex_content.mixed
    if extension = complex_content.extension
      setup_extension(extension, compiler_complex_type)
    elsif restriction = complex_content.restriction
      instance.restriction = setup_complex_content_restriction(restriction, compiler_complex_type)
    end
  end
end

#setup_complex_content_restriction(restriction, compiler_complex_type) ⇒ Object



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 335

def setup_complex_content_restriction(restriction, compiler_complex_type)
  ComplexContentRestriction.new.tap do |instance|
    compiler_complex_type.base_class = restriction.base
    resolved_element_order(restriction).each do |element|
      instance << case element
                  when Xsd::Attribute
                    setup_attribute(element)
                  when Xsd::AttributeGroup
                    setup_attribute_groups(element)
                  when Xsd::Sequence
                    setup_sequence(element)
                  when Xsd::Choice
                    setup_choice(element)
                  when Xsd::Group
                    setup_group_type(element)
                  end
    end
  end
end

#setup_complex_type(complex_type) ⇒ Object



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 172

def setup_complex_type(complex_type)
  ComplexType.new.tap do |instance|
    instance.id = complex_type.id
    instance.name = complex_type.name
    instance.mixed = complex_type.mixed
    resolved_element_order(complex_type).each do |element|
      case element
      when Xsd::Attribute
        instance << setup_attribute(element)
      when Xsd::Sequence
        instance << setup_sequence(element)
      when Xsd::Choice
        instance << setup_choice(element)
      when Xsd::ComplexContent
        instance << setup_complex_content(element, instance.name, instance)
      when Xsd::AttributeGroup
        instance << setup_attribute_groups(element)
      when Xsd::Group
        instance << setup_group_type(element)
      when Xsd::SimpleContent
        instance.simple_content = setup_simple_content(element)
      end
    end
  end
end

#setup_element(element) ⇒ Object



299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 299

def setup_element(element)
  element_name = element.name
  instance = Element.new(name: element_name, ref: element.ref)
  instance.min_occurs = element.min_occurs
  instance.max_occurs = element.max_occurs
  if element_name
    instance.type = setup_element_type(element, instance)
    instance.id = element.id
    instance.fixed = element.fixed
    instance.default = element.default
  end
  instance
end

#setup_element_type(element, _instance) ⇒ Object

Populates @simple_types or @complex_types based on elements available value.



314
315
316
317
318
319
320
321
322
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 314

def setup_element_type(element, _instance)
  return element.type if element.type

  type, prefix = element.simple_type ? ["simple", "ST"] : ["complex", "CT"]
  type_instance = element.public_send(:"#{type}_type")
  type_instance.name = [prefix, element.name].join("_")
  instance_variable_get(:"@#{type}_types")[type_instance.name] = public_send(:"setup_#{type}_type", type_instance)
  type_instance.name
end

#setup_extension(extension, instance) ⇒ Object



372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 372

def setup_extension(extension, instance)
  instance.base_class = extension.base
  resolved_element_order(extension).each do |element|
    instance << case element
                when Xsd::Attribute
                  setup_attribute(element)
                when Xsd::AttributeGroup
                  setup_attribute_groups(element)
                when Xsd::Sequence
                  setup_sequence(element)
                when Xsd::Choice
                  setup_choice(element)
                when Xsd::Group
                  setup_group_type(element)
                end
  end
end

#setup_group_type(group, root_call: false) ⇒ Object



230
231
232
233
234
235
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 230

def setup_group_type(group, root_call: false)
  object = Group.new(group.name, group.ref)
  object.instance = setup_group_type_instance(group)
  @group_types[group.name] = object if group.name && !root_call
  object
end

#setup_group_type_instance(group) ⇒ Object



237
238
239
240
241
242
243
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 237

def setup_group_type_instance(group)
  if sequence = group.sequence
    setup_sequence(sequence)
  elsif choice = group.choice
    setup_choice(choice)
  end
end

#setup_restriction(restriction) ⇒ Object



324
325
326
327
328
329
330
331
332
333
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 324

def setup_restriction(restriction)
  Restriction.new.tap do |instance|
    instance.base_class = restriction.base
    restriction_patterns(restriction.pattern, instance) if restriction.respond_to?(:pattern)
    restriction_content(instance, restriction)
    if restriction.respond_to?(:enumeration) && restriction.enumeration&.any?
      instance.enumerations = restriction.enumeration.map(&:value)
    end
  end
end

#setup_sequence(sequence) ⇒ Object



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 210

def setup_sequence(sequence)
  Sequence.new.tap do |instance|
    resolved_element_order(sequence).each do |object|
      # No implementation yet for Xsd::Any!
      next if object.is_a?(Xsd::Any)

      instance << case object
                  when Xsd::Sequence
                    setup_sequence(object)
                  when Xsd::Element
                    setup_element(object)
                  when Xsd::Choice
                    setup_choice(object)
                  when Xsd::Group
                    setup_group_type(object)
                  end
    end
  end
end

#setup_simple_content(simple_content) ⇒ Object



198
199
200
201
202
203
204
205
206
207
208
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 198

def setup_simple_content(simple_content)
  SimpleContent.new.tap do |instance|
    if simple_content.extension
      instance.base_class = simple_content.extension.base
      setup_extension(simple_content.extension, instance)
    elsif simple_content.restriction
      instance.base_class = simple_content.restriction.base
      instance << setup_restriction(simple_content.restriction)
    end
  end
end

#setup_simple_type(simple_type) ⇒ Object



129
130
131
132
133
134
135
136
137
138
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 129

def setup_simple_type(simple_type)
  SimpleType.new(simple_type.name).tap do |type_object|
    if union = simple_type.union
      type_object.unions = union.member_types.split
    elsif restriction = simple_type.restriction
      type_object.base_class = restriction.base&.split(":")&.last
      type_object.instance = setup_restriction(restriction)
    end
  end
end

#to_models(schema, options = {}) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/lutaml/model/schema/xml_compiler.rb', line 49

def to_models(schema, options = {})
  as_models(schema, options: options)
  options[:indent] = options[:indent] ? options[:indent].to_i : 2
  @simple_types.merge!(XmlCompiler::SimpleType.setup_supported_types)
  classes_list = @simple_types.merge(@complex_types).merge(@group_types)
  classes_list = classes_list.transform_values { |type| type.to_class(options: options) }
  if options[:create_files]
    dir = options.fetch(:output_dir, "lutaml_models_#{Time.now.to_i}")
    FileUtils.mkdir_p(dir)
    classes_list.each { |name, klass| create_file(name, klass, dir) }
    true
  else
    require_classes(classes_list) if options[:load_classes]
    classes_list
  end
end