rgen-xsd 0.1.0

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.
@@ -0,0 +1,83 @@
1
+ require "rgen/xsd/xsi_instantiator"
2
+ require "rgen/instantiator/reference_resolver"
3
+
4
+ module RGen
5
+ module XSD
6
+
7
+ class XSDInstantiator
8
+ attr_reader :unresolved_refs
9
+
10
+ def initialize(env, mm)
11
+ @env = env
12
+ @mm = mm
13
+ @unresolved_refs = []
14
+ @resolver = RGen::Instantiator::ReferenceResolver.new
15
+ end
16
+
17
+ def instantiate(file_name)
18
+ inst = XSIInstantiator.new(@mm, @env)
19
+ schema = inst.instantiate(file_name,
20
+ :root_class => XMLSchemaMetamodel::SchemaTYPE
21
+ )
22
+ urefs = inst.unresolved_refs
23
+ urefs.each do |ur|
24
+ ti = ur.proxy.targetIdentifier
25
+ href, name = inst.resolve_namespace(ti)
26
+ if href
27
+ ur.proxy.targetIdentifier = "#{href}:#{name}"
28
+ else
29
+ # either namespace not found (warning in resolve_namespace) or no namespace at all (not even default namespace)
30
+ end
31
+ end
32
+ @unresolved_refs += urefs
33
+ (schema.element +
34
+ schema.group +
35
+ schema.complexType +
36
+ schema.simpleType +
37
+ schema.attribute +
38
+ schema.attributeGroup).each do |e|
39
+ if e.name
40
+ if schema.targetNamespace
41
+ @resolver.add_identifier("#{schema.targetNamespace}:#{e.name}", e)
42
+ else
43
+ @resolver.add_identifier(e.name, e)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def resolve(problems=nil)
50
+ @unresolved_refs = @resolver.resolve(unresolved_refs, :problems => [], :use_target_type => true)
51
+ add_missing_builtin_types
52
+ @unresolved_refs = @resolver.resolve(unresolved_refs, :problems => problems, :use_target_type => true)
53
+ end
54
+
55
+ def add_missing_builtin_types
56
+ unresolved_refs.each do |ur|
57
+ if ur.proxy.targetIdentifier =~ /^http:\/\/www\.w3\.org\/2001\/XMLSchema:(\w+)$/
58
+ name = $1
59
+ if [ "anyType", "anySimpleType", "string", "normalizedString", "token", "language", "Name", "NCName",
60
+ "ID", "IDREF", "ENTITY", "NMTOKEN", "base64Binary", "hexBinary", "anyURI", "QName",
61
+ "NOTATION", "duration", "dateTime", "time", "date", "gYearMonth", "gYear", "gMonthDay",
62
+ "gDay", "gMonth", "IDREFS", "ENTITIES", "NMTOKENS", "float", "double",
63
+ "decimal", "integer", "nonPositiveInteger", "negativeInteger", "long", "int", "short",
64
+ "byte", "nonNegativeInteger", "unsignedLong", "unsignedInt", "unsignedShort",
65
+ "unsignedByte", "positiveInteger", "boolean" ].include?(name)
66
+ add_builtin_type(ur.proxy.targetIdentifier, name)
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ def add_builtin_type(target_identifier, name)
73
+ @builtin_type_added ||= {}
74
+ return if @builtin_type_added[target_identifier]
75
+ type = @env.new(XMLSchemaMetamodel::TopLevelSimpleType, :name => name)
76
+ @resolver.add_identifier(target_identifier, type)
77
+ @builtin_type_added[target_identifier] = true
78
+ end
79
+
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,325 @@
1
+ require 'rgen/ecore/ecore'
2
+ require 'rgen/util/name_helper'
3
+ require 'rgen/transformer'
4
+ require 'rgen/xsd/particle'
5
+ require 'rgen/xsd/simple_type'
6
+
7
+ module RGen
8
+ module XSD
9
+
10
+ # Mapping Strategy:
11
+ #
12
+ # 1. Complex Types are mapped to classes
13
+ #
14
+ # * this is true even for Complex Types with simple content
15
+ # (because they may contain attributes and the simple value)
16
+ # * the name of the class is the name of the Complex Type or
17
+ # a derived name (TODO) in case of an anonymous Complex Type
18
+ # * if the Complex Type is abstract, the class will be abstract
19
+ #
20
+ # 1.1 Extension is mapped to supertype relationship
21
+ #
22
+ # * the extending Complex Type contributes features according to its content
23
+ # (simple or complex content, simple content for example contributes attribtes)
24
+ #
25
+ # 1.2 Restriction is mapped to supertype relationship
26
+ #
27
+ # * the restriction expresses a "is a" relationship
28
+ # ("it is a <super type> but with restrictions")
29
+ # in general, restrictions can not be expressed in ECore, so
30
+ # we accept to have a superset of the original constraints
31
+ # * in some cases however, the restricting Complex Type many also contribute features
32
+ # e.g. in case the base complex type has a 'any' wildcard, then the
33
+ # restricting type may narrow this by specifying specific elements
34
+ # * the implementation idea is to add all features introduced by the restriction, just
35
+ # in the same way as with the extension, but exclude any features which have
36
+ # already been defined in a super class (i.e. new features may be added but
37
+ # existing features can not be restricted)
38
+ #
39
+ # 1.3 Element Particles
40
+ #
41
+ # * element particles are collected recursively through the type's content model:
42
+ # - Group references are replaced by the target group's child and min/max occurrence
43
+ # from the Group reference are applied to that replacement
44
+ # - Element references are replaced by the target element and min/max occurrence
45
+ # from the Element reference are applied to that replacement
46
+ # - recursion for model groups "all", "sequence", "choice"
47
+ # * multiplicities are calculated from the element particles' min/max occurrence
48
+ # and the min/max occurrence of the group particles they are contained in:
49
+ # - the min multiplicity of an element within a group is the element's min multiplicity
50
+ # multiplied by the groups min multiplicity
51
+ # - in case of a "choice" group, if an element occurrs in only one of several choices,
52
+ # its min multiplicity is 0
53
+ # - the max multiplicity of an element within a group is the element's max multiplicity
54
+ # multiplied by the groups max multiplicity
55
+ # * particles referencing elements which are substitution group heads are augmented
56
+ # by one other particle for each substitutable element, min/max occurrences are the
57
+ # same as for the heading element
58
+ #
59
+ # 1.3.1 Element Particles with Complex Type are mapped to containment references
60
+ #
61
+ # 1.3.2 Element Particles with Simple Type are mapped to to attributes
62
+ #
63
+ # 1.4 Wildcards (any)
64
+ #
65
+ # * there can be one wildcard attribute for each class created from a complex type
66
+ # by convention, the attribute is called "anyObject"; the attribute type is EObject
67
+ #
68
+ # * 'any' particles are collected recursively through the type's content model just
69
+ # as with element particles; however, in the end all of them will be condensed into one
70
+ # the multiplicity is calculated in the same way as for element particles
71
+ #
72
+ # * Note: depending on the value of the "processContents" attribute, XML instances may
73
+ # be forced to use only existing types in the "any" section (value "strict"); this
74
+ # means that all possible target types of the attribute would be known; so in theory
75
+ # the target type could be a superclass of all possible complex types (using "strict",
76
+ # XML instances may either make use of a toplevel element definition or any complex
77
+ # type using the xsi:type attribute)
78
+ #
79
+ # 1.5 Attributes are mapped to attributes
80
+ #
81
+ # 2. Simples Types are mapped to datatypes
82
+ #
83
+ # 2.1 Simple Types with an enumeration restriction are mapped to enums
84
+ #
85
+ # Mapping Variants:
86
+ #
87
+ # * Use content of complex type restrictions to create the actual class
88
+ # this means that superclasses can not also contain the same features
89
+ # so either the superclasses must be abstract or the superclass must be split into an
90
+ # abstract class and a concrete class derived from it
91
+ # UseCase: Facets in XSD, e.g. a NumFacet has a value of type integer
92
+ #
93
+ # * Use substitution groups to derive a class hierarchy
94
+ # In XSD, elements which are substitutes must have a derived type which is either a restriction
95
+ # or extension of the group head element or the same type
96
+ # Problem: substitution is defined on roles (elements), there can be substitutions which
97
+ # only differ in the role/element name, not in the type. As a solution there could be
98
+ # dedicated subclasses for this situation which hold the role information
99
+ # UseCase: Facets in XSD
100
+ #
101
+ class XSDToEcoreTransformer < RGen::Transformer
102
+ include RGen::ECore
103
+ include RGen::Util::NameHelper
104
+ include Particle
105
+ include SimpleType
106
+
107
+ # Options:
108
+ #
109
+ # :class_name_provider:
110
+ # a proc which receives a ComplexType and should return the EClass name
111
+ #
112
+ # :feature_name_provider:
113
+ # a proc which receives an Element or Attribute and should return the EStructuralFeature name
114
+ #
115
+ # :enum_name_provider:
116
+ # a proc which receives a SimpleType and should return the EEnum name
117
+ #
118
+ def initialize(env_in, env_out, options={})
119
+ super(env_in, env_out)
120
+ @package_by_source_element = {}
121
+ @class_name_provider = options[:class_name_provider] || proc do |type|
122
+ firstToUpper(type.name || type.containingElement.name+"TYPE")
123
+ end
124
+ @feature_name_provider = options[:feature_name_provider] || proc do |ea|
125
+ firstToLower(ea.name)
126
+ end
127
+ @enum_name_provider = options[:enum_name_provider] || proc do |type|
128
+ uniq_classifier_name(@package_by_source_element[type],
129
+ type.name ? firstToUpper(type.name)+"Enum" :
130
+ type.containingAttribute ? firstToUpper(type.containingAttribute.effectiveAttribute.name)+"Enum" :
131
+ "Unknown")
132
+ end
133
+ end
134
+
135
+ def transform
136
+ root = @env_out.new(RGen::ECore::EPackage, :name => "MM")
137
+ schemas = @env_in.find(:class => XMLSchemaMetamodel::SchemaTYPE)
138
+ schemas.each do |s|
139
+ tns = s.targetNamespace || "Default"
140
+ name = tns.sub(/http:\/\/(www\.)?/,"").split("/").
141
+ collect{|p| p.split(/\W/).collect{|t| firstToUpper(t)}.join }.join
142
+ puts "empty package name" if name.strip.empty?
143
+ p = package_by_name(name, root, tns)
144
+ child_elements(s).each{|e| @package_by_source_element[e] = p}
145
+ end
146
+ trans(schemas.complexType)
147
+ trans(schemas.element.effectiveType.select do |t|
148
+ t.is_a?(XMLSchemaMetamodel::ComplexType) ||
149
+ build_type_desc(t).type.is_a?(Array)
150
+ end)
151
+
152
+ # remove duplicate features created by restrictions
153
+ # TODO: find a way to do this while transformation or make sure unused elements will also be removed (EClasses, EAnnotation, ..)
154
+ root.eAllClasses.each do |c|
155
+ super_features = c.eAllStructuralFeatures - c.eStructuralFeatures
156
+ c.eStructuralFeatures.each do |f|
157
+ if super_features.any?{|sf| sf.name == f.name}
158
+ f.eContainingClass = nil
159
+ f.eType = nil
160
+ f.eOpposite = nil if f.is_a?(RGen::ECore::EReference)
161
+ @env_out.delete(f)
162
+ end
163
+ end
164
+ end
165
+
166
+ # unique classifier names
167
+ # TODO: smarter way to modify names
168
+ root.eAllSubpackages.each do |p|
169
+ names = {}
170
+ p.eClassifiers.each do |c|
171
+ while names[c.name]
172
+ c.name = c.name+"X"
173
+ end
174
+ names[c.name] = true
175
+ end
176
+ end
177
+
178
+ root
179
+ end
180
+
181
+ def package_by_name(name, root, ns)
182
+ @package_by_name ||= {}
183
+ @package_by_name[name] ||= @env_out.new(RGen::ECore::EPackage, :name => name, :eSuperPackage => root,
184
+ :eAnnotations => [create_annotation("xmlName", ns)])
185
+ end
186
+
187
+ transform XMLSchemaMetamodel::ComplexType, :to => EClass do
188
+ _features = []
189
+ if complexContent.andand.extension
190
+ _particles = element_particles(complexContent.extension)
191
+ elsif complexContent.andand.restriction
192
+ _particles = element_particles(complexContent.restriction)
193
+ else
194
+ _particles = element_particles(@current_object)
195
+ end
196
+ _particles = add_substitution_particles(_particles)
197
+ _particles.each do |p|
198
+ if p.kind == :element
199
+ e = p.node
200
+ if e.effectiveType.is_a?(XMLSchemaMetamodel::ComplexType)
201
+ fn = @feature_name_provider.call(e)
202
+ _features << @env_out.new(RGen::ECore::EReference, :name => fn, :containment => true,
203
+ :upperBound => p.maxOccurs == "unbounded" ? -1 : p.maxOccurs,
204
+ :lowerBound => p.minOccurs,
205
+ :eType => trans(e.effectiveType))
206
+ if fn != e.name
207
+ _features.last.eAnnotations = [create_annotation("xmlName", e.name)]
208
+ end
209
+ elsif e.effectiveType.is_a?(XMLSchemaMetamodel::SimpleType)
210
+ _features << create_attribute(e)
211
+ end
212
+ else
213
+ # any
214
+ _features << @env_out.new(RGen::ECore::EAttribute, :name => "anyObject",
215
+ :lowerBound => p.minOccurs,
216
+ :upperBound => p.maxOccurs == "unbounded" ? -1 : p.maxOccurs,
217
+ :eType => RGen::ECore::ERubyObject)
218
+ end
219
+ end
220
+ allAttributes.effectiveAttribute.each do |a|
221
+ _features << create_attribute(a)
222
+ end
223
+ if mixed
224
+ _features << @env_out.new(RGen::ECore::EAttribute, :name => "text",
225
+ :eType => RGen::ECore::EString,
226
+ :eAnnotations => [create_annotation("simpleContent", "true")])
227
+ end
228
+ if simpleContent.andand.extension.andand.base || simpleContent.andand.restriction.andand.base
229
+ _features << @env_out.new(RGen::ECore::EAttribute, :name => "simpleValue",
230
+ :eType => get_datatype(simple_content_base_type(@current_object)),
231
+ :eAnnotations => [create_annotation("simpleContent", "true")])
232
+ end
233
+ { :name => @class_name_provider.call(@current_object),
234
+ :abstract => abstract,
235
+ :eStructuralFeatures => _features,
236
+ :eSuperTypes => trans([complexContent.andand.extension.andand.base ||
237
+ complexContent.andand.restriction.andand.base].compact.select{|t| t.is_a?(XMLSchemaMetamodel::ComplexType)}),
238
+ :ePackage => @package_by_source_element[@current_object],
239
+ :eAnnotations => name ? [create_annotation("xmlName", name)] : []
240
+ }
241
+ end
242
+
243
+ def simple_content_base_type(type)
244
+ base = type.simpleContent.andand.extension.andand.base || type.simpleContent.andand.restriction.andand.base
245
+ while base.is_a?(XMLSchemaMetamodel::ComplexType)
246
+ base = base.simpleContent.andand.extension.andand.base || base.simpleContent.andand.restriction.andand.base
247
+ end
248
+ base
249
+ end
250
+
251
+ def create_annotation(key, value)
252
+ @env_out.new(RGen::ECore::EAnnotation, :source => "xsd", :details =>
253
+ [@env_out.new(RGen::ECore::EStringToStringMapEntry, :key => key, :value => value)])
254
+ end
255
+
256
+ def create_attribute(e)
257
+ td = build_type_desc(e.effectiveType)
258
+ fn = @feature_name_provider.call(e)
259
+ result = @env_out.new(RGen::ECore::EAttribute, :name => fn,
260
+ :lowerBound => td.minOccurs,
261
+ :upperBound => td.maxOccurs,
262
+ :eType => get_datatype(e.effectiveType))
263
+ if fn != e.name
264
+ result.eAnnotations = [create_annotation("xmlName", e.name)]
265
+ end
266
+ result
267
+ end
268
+
269
+ def get_datatype(type)
270
+ td = build_type_desc(type)
271
+ case td.type
272
+ when :string
273
+ RGen::ECore::EString
274
+ when :int
275
+ RGen::ECore::EInt
276
+ when :float
277
+ RGen::ECore::EFloat
278
+ when :boolean
279
+ RGen::ECore::EBoolean
280
+ when :object
281
+ RGen::ECore::ERubyObject
282
+ when Array
283
+ trans(type)
284
+ end
285
+ end
286
+
287
+ transform XMLSchemaMetamodel::SimpleType, :to => EEnum do
288
+ _literals = build_type_desc(@current_object).type
289
+ raise "not an enum: #{@current_object.class}" unless _literals.is_a?(Array)
290
+ { :name => @enum_name_provider.call(@current_object),
291
+ :eLiterals => _literals.collect{|l| @env_out.new(RGen::ECore::EEnumLiteral, :name => l)},
292
+ :ePackage => @package_by_source_element[@current_object]
293
+ }
294
+ end
295
+
296
+ def uniq_classifier_name(package, base)
297
+ try = base
298
+ index = 2
299
+ while package.eClassifiers.any?{|c| c.name == try}
300
+ try = base + index.to_s
301
+ index += 1
302
+ end
303
+ try
304
+ end
305
+
306
+ def child_elements(element, opts={})
307
+ result = []
308
+ element.class.ecore.eAllReferences.each do |r|
309
+ if r.containment
310
+ element.getGenericAsArray(r.name).each do |e|
311
+ if !opts[:class] || e.is_a?(opts[:class])
312
+ result << e
313
+ end
314
+ result += child_elements(e, opts)
315
+ end
316
+ end
317
+ end
318
+ result
319
+ end
320
+
321
+ end
322
+
323
+ end
324
+ end
325
+
@@ -0,0 +1,230 @@
1
+ require 'nokogiri'
2
+ require 'andand'
3
+ require 'rgen/ecore/ecore'
4
+ require 'rgen/instantiator/reference_resolver'
5
+
6
+ module RGen
7
+ module XSD
8
+
9
+ class XSIInstantiator
10
+ attr_reader :unresolved_refs
11
+ attr_reader :namespaces
12
+
13
+ def initialize(mm, env)
14
+ @mm = mm
15
+ @env = env
16
+ @classes_by_xml_name = nil
17
+ @namespaces = []
18
+ end
19
+
20
+ def instantiate(file, options={})
21
+ @unresolved_refs = []
22
+ root = nil
23
+ root_class = options[:root_class].andand.ecore || root_class(doc.root.name)
24
+ File.open(file) do |f|
25
+ doc = Nokogiri::XML(f)
26
+ @namespaces = doc.root.namespace_definitions
27
+ root =instantiate_node(doc.root, root_class)
28
+ end
29
+ root
30
+ end
31
+
32
+ def resolve_namespace(str)
33
+ if str =~ /:/
34
+ prefix, name = str.split(":")
35
+ else
36
+ prefix, name = nil, str
37
+ end
38
+ # the default namespace has a prefix of nil
39
+ href = namespaces.find{|ns| ns.prefix == prefix}.andand.href
40
+ # built in xml schema namespace
41
+ if !href
42
+ if prefix == "xml"
43
+ href = "http://www.w3.org/XML/1998/namespace"
44
+ elsif prefix
45
+ puts "WARN: Can not resolve namespace prefix #{prefix}"
46
+ end
47
+ end
48
+ [href, name]
49
+ end
50
+
51
+ private
52
+
53
+ def xsi_type_value(node)
54
+ node.attribute_nodes.find{|n| is_xsi_type?(n)}.andand.text
55
+ end
56
+
57
+ def instantiate_node(node, eclass)
58
+ element = eclass.instanceClass.new
59
+ @env << element
60
+ set_attribute_values(element, node)
61
+ simple_content = ""
62
+ can_take_any = eclass.eAllAttributes.any?{|a| a.name == "anyObject"}
63
+ node.children.each do |c|
64
+ if c.text?
65
+ simple_content << c.text
66
+ elsif c.element?
67
+ feats = features_by_xml_name(c.name).select{|f|
68
+ f.eContainingClass == element.class.ecore ||
69
+ f.eContainingClass.eAllSubTypes.include?(element.class.ecore)
70
+ }
71
+ if feats.size == 1
72
+ begin
73
+ if feats.first.is_a?(RGen::ECore::EReference)
74
+ element.setOrAddGeneric(feats.first.name,
75
+ instantiate_node(c, reference_target_type(feats.first, xsi_type_value(c))))
76
+ else
77
+ element.setOrAddGeneric(feats.first.name, value_from_string(element, feats.first, c.text))
78
+ end
79
+ rescue Exception => e
80
+ puts "Line: #{node.line}: #{e}"
81
+ end
82
+ else
83
+ if can_take_any
84
+ begin
85
+ # currently the XML node is added to the model
86
+ element.setOrAddGeneric("anyObject", c)
87
+ rescue Exception => e
88
+ puts "Line: #{node.line}: #{e}"
89
+ end
90
+ else
91
+ problem "could not determine reference for tag #{c.name}, #{feats.size} options", node
92
+ end
93
+ end
94
+ end
95
+ end
96
+ if simple_content.strip.size > 0
97
+ set_simple_content(element, simple_content.strip)
98
+ end
99
+ element
100
+ end
101
+
102
+ def set_simple_content(element, value)
103
+ a = element.class.ecore.eAllAttributes.find{|a| annotation_value(a, "simpleContent") == "true"}
104
+ if a
105
+ element.setGeneric(a.name, value_from_string(element, a, value))
106
+ else
107
+ raise "could not set simple content for element #{element.class.name}"
108
+ end
109
+ end
110
+
111
+ def is_xsi_type?(attr_node)
112
+ attr_node.namespace.andand.href == "http://www.w3.org/2001/XMLSchema-instance" && attr_node.node_name == "type"
113
+ end
114
+
115
+ def set_attribute_values(element, node)
116
+ node.attribute_nodes.each do |attrnode|
117
+ next if is_xsi_type?(attrnode)
118
+ name = attrnode.node_name
119
+ feats = (features_by_xml_name(name) || []).select{|f|
120
+ f.eContainingClass == element.class.ecore ||
121
+ f.eContainingClass.eAllSubTypes.include?(element.class.ecore)}
122
+ if feats.size == 1
123
+ f = feats.first
124
+ str = node.attr(name)
125
+ if f.many
126
+ # list datatype implies whitespace handling method "collapse",
127
+ # removing white space in the front and back and reducing multiple whitespaces to one
128
+ # list items are separated by spaces
129
+ str.strip.split(/\s+/).each do |s|
130
+ element.addGeneric(f.name, value_from_string(element, f, s))
131
+ end
132
+ else
133
+ element.setGeneric(f.name, value_from_string(element, f, str))
134
+ end
135
+ elsif name == "schemaLocation" || name == "noNamespaceSchemaLocation"
136
+ # ignore, this may occure with any XML element
137
+ else
138
+ problem "could not determine feature for attribute #{name}, #{feats.size} options", node
139
+ end
140
+ end
141
+ end
142
+
143
+ def value_from_string(element, f, str)
144
+ if f.is_a?(RGen::ECore::EAttribute)
145
+ case f.eType
146
+ when RGen::ECore::EInt
147
+ str.to_i
148
+ when RGen::ECore::EFloat
149
+ str.to_f
150
+ when RGen::ECore::EEnum
151
+ str.to_sym
152
+ when RGen::ECore::EBoolean
153
+ (str == "true")
154
+ else
155
+ str
156
+ end
157
+ else
158
+ proxy = RGen::MetamodelBuilder::MMProxy.new(str)
159
+ @unresolved_refs <<
160
+ RGen::Instantiator::ReferenceResolver::UnresolvedReference.new(element, f.name, proxy)
161
+ proxy
162
+ end
163
+ end
164
+
165
+ def reference_target_type(ref, typename)
166
+ if typename
167
+ href, name = resolve_namespace(typename)
168
+ href ||= "Default"
169
+ types = (ref.eType.eAllSubTypes + [ref.eType]) & classes_by_xml_name(name).select{|c| xml_name(c.ePackage) == href}
170
+ if types.size == 1
171
+ types.first
172
+ elsif types.size > 1
173
+ raise "ambiguous type name #{typename}: #{types.collect{|t| t.name}.join(",")}"
174
+ else
175
+ raise "type name #{typename} not found"
176
+ end
177
+ else
178
+ ref.eType
179
+ end
180
+ end
181
+
182
+ def root_class(tag_name)
183
+ classes = classes_by_xml_name(tag_name) || []
184
+ if classes.size == 1
185
+ classes.first
186
+ else
187
+ raise "could not determine root class, #{classes.size} options"
188
+ end
189
+ end
190
+
191
+ def problem(desc, node)
192
+ raise desc + " at [#{node.name}]"
193
+ end
194
+
195
+ def classes_by_xml_name(name)
196
+ return @classes_by_xml_name[name] || [] if @classes_by_xml_name
197
+ @classes_by_xml_name = {}
198
+ @mm.ecore.eAllClasses.each do |c|
199
+ n = xml_name(c)
200
+ @classes_by_xml_name[n] ||= []
201
+ @classes_by_xml_name[n] << c
202
+ end
203
+ @classes_by_xml_name[name]
204
+ end
205
+
206
+ def features_by_xml_name(name)
207
+ return @features_by_xml_name[name] || [] if @features_by_xml_name
208
+ @features_by_xml_name = {}
209
+ @mm.ecore.eAllClasses.eStructuralFeatures.each do |f|
210
+ n = xml_name(f)
211
+ @features_by_xml_name[n] ||= []
212
+ @features_by_xml_name[n] << f
213
+ end
214
+ @features_by_xml_name[name]
215
+ end
216
+
217
+ def xml_name(o)
218
+ annotation_value(o, "xmlName") || o.name
219
+ end
220
+
221
+ def annotation_value(o, key)
222
+ anno = o.eAnnotations.find{|a| a.source == "xsd"}
223
+ anno.andand.details.andand.find{|d| d.key == key}.andand.value
224
+ end
225
+
226
+ end
227
+
228
+ end
229
+ end
230
+