Class: Shale::Schema::XMLCompiler

Inherits:
Object
  • Object
show all
Defined in:
lib/shale/schema/xml_compiler.rb

Overview

Class for compiling XML schema into Ruby data model

Constant Summary collapse

XML_NAMESPACE_URI =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML namespace URI

'http://www.w3.org/XML/1998/namespace'
XML_NAMESPACE_PREFIX =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML namespace prefix

'xml'
XS_NAMESPACE_URI =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema namespace URI

'http://www.w3.org/2001/XMLSchema'
XS_SCHEMA =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema “schema” element name

"#{XS_NAMESPACE_URI}:schema".freeze
XS_ELEMENT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema “element” element name

"#{XS_NAMESPACE_URI}:element".freeze
XS_ATTRIBUTE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema “attribute” element name

"#{XS_NAMESPACE_URI}:attribute".freeze
XS_SIMPLE_TYPE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema “attribute” element name

"#{XS_NAMESPACE_URI}:simpleType".freeze
XS_SIMPLE_CONTENT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema “simpleContent” element name

"#{XS_NAMESPACE_URI}:simpleContent".freeze
XS_RESTRICTION =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema “restriction” element name

"#{XS_NAMESPACE_URI}:restriction".freeze
XS_GROUP =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema “group” element name

"#{XS_NAMESPACE_URI}:group".freeze
XS_ATTRIBUTE_GROUP =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema “attributeGroup” element name

"#{XS_NAMESPACE_URI}:attributeGroup".freeze
XS_COMPLEX_TYPE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema “complexType” element name

"#{XS_NAMESPACE_URI}:complexType".freeze
XS_COMPLEX_CONTENT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema “complexContent” element name

"#{XS_NAMESPACE_URI}:complexContent".freeze
XS_EXTENSION =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema “extension” element name

"#{XS_NAMESPACE_URI}:extension".freeze
XS_TYPE_ANY =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema “anyType” type

"#{XS_NAMESPACE_URI}:anyType".freeze
XS_TYPE_DATE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema “date” types

[
  "#{XS_NAMESPACE_URI}:date",
].freeze
XS_TYPE_TIME =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema “datetime” types

[
  "#{XS_NAMESPACE_URI}:dateTime",
].freeze
XS_TYPE_STRING =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema “string” types

[
  "#{XS_NAMESPACE_URI}:string",
  "#{XS_NAMESPACE_URI}:normalizedString",
  "#{XS_NAMESPACE_URI}:token",
].freeze
XS_TYPE_FLOAT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema “float” types

[
  "#{XS_NAMESPACE_URI}:decimal",
  "#{XS_NAMESPACE_URI}:float",
  "#{XS_NAMESPACE_URI}:double",
].freeze
XS_TYPE_INTEGER =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema “integer” types

[
  "#{XS_NAMESPACE_URI}:integer",
  "#{XS_NAMESPACE_URI}:byte",
  "#{XS_NAMESPACE_URI}:int",
  "#{XS_NAMESPACE_URI}:long",
  "#{XS_NAMESPACE_URI}:negativeInteger",
  "#{XS_NAMESPACE_URI}:nonNegativeInteger",
  "#{XS_NAMESPACE_URI}:nonPositiveInteger",
  "#{XS_NAMESPACE_URI}:positiveInteger",
  "#{XS_NAMESPACE_URI}:short",
  "#{XS_NAMESPACE_URI}:unsignedLong",
  "#{XS_NAMESPACE_URI}:unsignedInt",
  "#{XS_NAMESPACE_URI}:unsignedShort",
  "#{XS_NAMESPACE_URI}:unsignedByte",
].freeze
XS_TYPE_BOOLEAN =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

XML Schema “boolean” types

[
  "#{XS_NAMESPACE_URI}:boolean",
].freeze
MODEL_TEMPLATE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Shale model template

ERB.new(<<~TEMPLATE, trim_mode: '-')
  require 'shale'
  <%- unless type.references.empty? -%>

  <%- type.references.each do |property| -%>
  require_relative '<%= type.relative_path(property.type.file_name) %>'
  <%- end -%>
  <%- end -%>

  <%- type.modules.each_with_index do |name, i| -%>
  <%= '  ' * i %>module <%= name %>
  <%- end -%>
  <%- indent = '  ' * type.modules.length -%>
  <%= indent %>class <%= type.root_name %> < Shale::Mapper
    <%- type.properties.select(&:content?).each do |property| -%>
    <%= indent %>attribute :<%= property.attribute_name %>, <%= property.type.name -%>
    <%- if property.collection? %>, collection: true<% end -%>
    <%- unless property.default.nil? %>, default: -> { <%= property.default %> }<% end %>
    <%- end -%>
    <%- type.properties.select(&:attribute?).each do |property| -%>
    <%= indent %>attribute :<%= property.attribute_name %>, <%= property.type.name -%>
    <%- if property.collection? %>, collection: true<% end -%>
    <%- unless property.default.nil? %>, default: -> { <%= property.default %> }<% end %>
    <%- end -%>
    <%- type.properties.select(&:element?).each do |property| -%>
    <%= indent %>attribute :<%= property.attribute_name %>, <%= property.type.name -%>
    <%- if property.collection? %>, collection: true<% end -%>
    <%- unless property.default.nil? %>, default: -> { <%= property.default %> }<% end %>
    <%- end -%>

    <%= indent %>xml do
      <%= indent %>root '<%= type.root %>'
      <%- if type.namespace -%>
      <%= indent %>namespace '<%= type.namespace %>', '<%= type.prefix %>'
      <%- end -%>
      <%- unless type.properties.empty? -%>

      <%- type.properties.select(&:content?).each do |property| -%>
      <%= indent %>map_content to: :<%= property.attribute_name %>
      <%- end -%>
      <%- type.properties.select(&:attribute?).each do |property| -%>
      <%= indent %>map_attribute '<%= property.mapping_name %>', to: :<%= property.attribute_name -%>
      <%- if property.namespace %>, prefix: '<%= property.prefix %>'<%- end -%>
      <%- if property.namespace %>, namespace: '<%= property.namespace %>'<% end %>
      <%- end -%>
      <%- type.properties.select(&:element?).each do |property| -%>
      <%= indent %>map_element '<%= property.mapping_name %>', to: :<%= property.attribute_name -%>
      <%- if type.namespace != property.namespace %>, prefix: <%= property.prefix ? "'\#{property.prefix}'" : 'nil' %><%- end -%>
      <%- if type.namespace != property.namespace %>, namespace: <%= property.namespace ? "'\#{property.namespace}'" : 'nil' %><% end %>
      <%- end -%>
      <%- end -%>
    <%= indent %>end
  <%= indent %>end
  <%- type.modules.length.times do |i| -%>
  <%= '  ' * (type.modules.length - i - 1) %>end
  <%- end -%>
TEMPLATE

Instance Method Summary collapse

Instance Method Details

#as_models(schemas, namespace_mapping: nil) ⇒ Array<Shale::Schema::Compiler::XMLComplex>

Generate Shale models from XML Schema and return them as a Ruby Array of objects

Examples:

Shale::Schema::XMLCompiler.new.as_models([schema1, schema2])

Parameters:

  • schemas (Array<String>)
  • namespace_mapping (Hash<String, String>, nil) (defaults to: nil)

Returns:

Raises:

  • (AdapterError)

    when XML adapter is not set or Ox adapter is used

  • (SchemaError)

    when XML Schema has errors



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/shale/schema/xml_compiler.rb', line 209

def as_models(schemas, namespace_mapping: nil)
  unless Shale.xml_adapter
    raise AdapterError, XML_ADAPTER_NOT_SET_MESSAGE
  end

  if Shale.xml_adapter.name == 'Shale::Adapter::Ox'
    msg = "Ox doesn't support XML namespaces and can't be used to compile XML Schema"
    raise AdapterError, msg
  end

  schemas = schemas.map do |schema|
    Shale.xml_adapter.load(schema)
  end

  @namespace_mapping = namespace_mapping || {}
  @elements_repository = {}
  @attributes_repository = {}
  @simple_types_repository = {}
  @element_groups_repository = {}
  @attribute_groups_repository = {}
  @complex_types_repository = {}
  @complex_types = {}
  @types = []

  schemas.each do |schema|
    build_repositories(schema)
  end

  resolve_nested_refs(@simple_types_repository)

  schemas.each do |schema|
    compile(schema)
  end

  @types = @types.uniq

  total_duplicates = Hash.new(0)
  duplicates = Hash.new(0)

  @types.each do |type|
    total_duplicates[type.name] += 1
  end

  @types.each do |type|
    duplicates[type.name] += 1

    if total_duplicates[type.name] > 1
      type.root_name = format("#{type.root_name}%d", duplicates[type.name])
    end
  end

  @types.reverse
rescue ParseError => e
  raise SchemaError, "XML Schema document is invalid: #{e.message}"
end

#to_models(schemas, namespace_mapping: nil) ⇒ Hash<String, String>

Generate Shale models from XML Schema

Examples:

Shale::Schema::XMLCompiler.new.to_models([schema1, schema2])

Parameters:

  • schemas (Array<String>)
  • namespace_mapping (Hash<String, String>, nil) (defaults to: nil)

Returns:

  • (Hash<String, String>)

Raises:



278
279
280
281
282
283
284
# File 'lib/shale/schema/xml_compiler.rb', line 278

def to_models(schemas, namespace_mapping: nil)
  types = as_models(schemas, namespace_mapping: namespace_mapping)

  types.to_h do |type|
    [type.file_name, MODEL_TEMPLATE.result(binding)]
  end
end