Class: RGen::XSD::XSDToEcoreTransformer

Inherits:
Transformer
  • Object
show all
Includes:
ECore, Util::NameHelper, Particle, SimpleType
Defined in:
lib/rgen/xsd/xsd_to_ecore.rb

Overview

Mapping Strategy:

  1. Complex Types are mapped to classes

  • this is true even for Complex Types with simple content (because they may contain attributes and the simple value)

  • the name of the class is the name of the Complex Type or a derived name (TODO) in case of an anonymous Complex Type

  • if the Complex Type is abstract, the class will be abstract

1.1 Extension is mapped to supertype relationship

  • the extending Complex Type contributes features according to its content (simple or complex content, simple content for example contributes attribtes)

1.2 Restriction is mapped to supertype relationship

  • the restriction expresses a “is a” relationship (“it is a <super type> but with restrictions”) in general, restrictions can not be expressed in ECore, so we accept to have a superset of the original constraints

  • in some cases however, the restricting Complex Type many also contribute features e.g. in case the base complex type has a ‘any’ wildcard, then the restricting type may narrow this by specifying specific elements

  • the implementation idea is to add all features introduced by the restriction, just in the same way as with the extension, but exclude any features which have already been defined in a super class (i.e. new features may be added but existing features can not be restricted)

1.3 Element Particles

  • element particles are collected recursively through the type’s content model:

    • Group references are replaced by the target group’s child and min/max occurrence from the Group reference are applied to that replacement

    • Element references are replaced by the target element and min/max occurrence from the Element reference are applied to that replacement

    • recursion for model groups “all”, “sequence”, “choice”

  • multiplicities are calculated from the element particles’ min/max occurrence and the min/max occurrence of the group particles they are contained in:

    • the min multiplicity of an element within a group is the element’s min multiplicity multiplied by the groups min multiplicity

    • in case of a “choice” group, if an element occurrs in only one of several choices, its min multiplicity is 0

    • the max multiplicity of an element within a group is the element’s max multiplicity multiplied by the groups max multiplicity

  • particles referencing elements which are substitution group heads are augmented by one other particle for each substitutable element, min/max occurrences are the same as for the heading element

1.3.1 Element Particles with Complex Type are mapped to containment references

1.3.2 Element Particles with Simple Type are mapped to to attributes

1.4 Wildcards (any)

  • there can be one wildcard attribute for each class created from a complex type by convention, the attribute is called “anyObject”; the attribute type is EObject

  • ‘any’ particles are collected recursively through the type’s content model just as with element particles; however, in the end all of them will be condensed into one the multiplicity is calculated in the same way as for element particles

  • Note: depending on the value of the “processContents” attribute, XML instances may be forced to use only existing types in the “any” section (value “strict”); this means that all possible target types of the attribute would be known; so in theory the target type could be a superclass of all possible complex types (using “strict”, XML instances may either make use of a toplevel element definition or any complex type using the xsi:type attribute)

1.5 Attributes are mapped to attributes

  1. Simples Types are mapped to datatypes

2.1 Simple Types with an enumeration restriction are mapped to enums

Mapping Variants:

  • Use content of complex type restrictions to create the actual class this means that superclasses can not also contain the same features so either the superclasses must be abstract or the superclass must be split into an abstract class and a concrete class derived from it UseCase: Facets in XSD, e.g. a NumFacet has a value of type integer

  • Use substitution groups to derive a class hierarchy In XSD, elements which are substitutes must have a derived type which is either a restriction or extension of the group head element or the same type Problem: substitution is defined on roles (elements), there can be substitutions which only differ in the role/element name, not in the type. As a solution there could be dedicated subclasses for this situation which hold the role information UseCase: Facets in XSD

Instance Method Summary collapse

Methods included from SimpleType

#build_type_desc, #builtin_type

Methods included from Particle

#add_substitution_particles, #build_particles_trees, #element_particles, #flatten_particle_tree

Constructor Details

#initialize(env_in, env_out, options = {}) ⇒ XSDToEcoreTransformer

Options:

:class_name_provider:

a proc which receives a ComplexType and should return the EClass name

:feature_name_provider:

a proc which receives an Element or Attribute and should return the EStructuralFeature name

:enum_name_provider:

a proc which receives a SimpleType and should return the EEnum name


118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/rgen/xsd/xsd_to_ecore.rb', line 118

def initialize(env_in, env_out, options={})
  super(env_in, env_out)
  @package_by_source_element = {}
  @class_name_provider = options[:class_name_provider] || proc do |type|
    firstToUpper(type.name || type.containingElement.name+"TYPE")
  end
  @feature_name_provider = options[:feature_name_provider] || proc do |ea|
    firstToLower(ea.name)
  end
  @enum_name_provider = options[:enum_name_provider] || proc do |type|
    uniq_classifier_name(@package_by_source_element[type], 
      type.name ? firstToUpper(type.name)+"Enum" : 
        type.containingAttribute ? firstToUpper(type.containingAttribute.effectiveAttribute.name)+"Enum" :
        "Unknown")
  end
end

Instance Method Details

#child_elements(element, opts = {}) ⇒ Object



306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/rgen/xsd/xsd_to_ecore.rb', line 306

def child_elements(element, opts={})
  result = []
  element.class.ecore.eAllReferences.each do |r|
    if r.containment 
      element.getGenericAsArray(r.name).each do |e|
        if !opts[:class] || e.is_a?(opts[:class])
          result << e
        end
        result += child_elements(e, opts)
      end
    end
  end
  result
end

#create_annotation(key, value) ⇒ Object



251
252
253
254
# File 'lib/rgen/xsd/xsd_to_ecore.rb', line 251

def create_annotation(key, value)
   @env_out.new(RGen::ECore::EAnnotation, :source => "xsd", :details => 
     [@env_out.new(RGen::ECore::EStringToStringMapEntry, :key => key, :value => value)])
end

#create_attribute(e) ⇒ Object



256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/rgen/xsd/xsd_to_ecore.rb', line 256

def create_attribute(e)
  td = build_type_desc(e.effectiveType)
  fn = @feature_name_provider.call(e)
  result = @env_out.new(RGen::ECore::EAttribute, :name => fn, 
    :lowerBound => td.minOccurs,
    :upperBound => td.maxOccurs,
    :eType => get_datatype(e.effectiveType))
  if fn != e.name
    result.eAnnotations = [create_annotation("xmlName", e.name)]
  end
  result
end

#get_datatype(type) ⇒ Object



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/rgen/xsd/xsd_to_ecore.rb', line 269

def get_datatype(type)
  td = build_type_desc(type)
  case td.type
  when :string
    RGen::ECore::EString
  when :int
    RGen::ECore::EInt
  when :float
    RGen::ECore::EFloat
  when :boolean
    RGen::ECore::EBoolean
  when :object
    RGen::ECore::ERubyObject
  when Array
    trans(type)
  end
end

#package_by_name(name, root, ns) ⇒ Object



181
182
183
184
185
# File 'lib/rgen/xsd/xsd_to_ecore.rb', line 181

def package_by_name(name, root, ns)
  @package_by_name ||= {}
  @package_by_name[name] ||= @env_out.new(RGen::ECore::EPackage, :name => name, :eSuperPackage => root,
    :eAnnotations => [create_annotation("xmlName", ns)])
end

#simple_content_base_type(type) ⇒ Object



243
244
245
246
247
248
249
# File 'lib/rgen/xsd/xsd_to_ecore.rb', line 243

def simple_content_base_type(type)
  base = type.simpleContent.andand.extension.andand.base || type.simpleContent.andand.restriction.andand.base
  while base.is_a?(XMLSchemaMetamodel::ComplexType)
    base = base.simpleContent.andand.extension.andand.base || base.simpleContent.andand.restriction.andand.base
  end
  base
end

#transformObject



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/rgen/xsd/xsd_to_ecore.rb', line 135

def transform
  root = @env_out.new(RGen::ECore::EPackage, :name => "MM")
  schemas = @env_in.find(:class => XMLSchemaMetamodel::SchemaTYPE)
  schemas.each do |s|
    tns = s.targetNamespace || "Default"
    name = tns.sub(/http:\/\/(www\.)?/,"").split("/").
      collect{|p| p.split(/\W/).collect{|t| firstToUpper(t)}.join }.join
    puts "empty package name" if name.strip.empty?
    p = package_by_name(name, root, tns)
    child_elements(s).each{|e| @package_by_source_element[e] = p}
  end
  trans(schemas.complexType)
  trans(schemas.element.effectiveType.select do |t|
    t.is_a?(XMLSchemaMetamodel::ComplexType) ||
    build_type_desc(t).type.is_a?(Array)
  end)

  # remove duplicate features created by restrictions
  # TODO: find a way to do this while transformation or make sure unused elements will also be removed (EClasses, EAnnotation, ..)
  root.eAllClasses.each do |c|
    super_features = c.eAllStructuralFeatures - c.eStructuralFeatures
    c.eStructuralFeatures.each do |f|
      if super_features.any?{|sf| sf.name == f.name}
        f.eContainingClass = nil
        f.eType = nil
        f.eOpposite = nil if f.is_a?(RGen::ECore::EReference)
        @env_out.delete(f)
      end
    end
  end

  # unique classifier names
  # TODO: smarter way to modify names
  root.eAllSubpackages.each do |p|
    names = {}
    p.eClassifiers.each do |c|
      while names[c.name]
        c.name = c.name+"X"
      end
      names[c.name] = true
    end
  end

  root
end

#uniq_classifier_name(package, base) ⇒ Object



296
297
298
299
300
301
302
303
304
# File 'lib/rgen/xsd/xsd_to_ecore.rb', line 296

def uniq_classifier_name(package, base)
  try = base
  index = 2
  while package.eClassifiers.any?{|c| c.name == try}
    try = base + index.to_s
    index += 1
  end
  try
end