lutaml-xmi 0.2.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,354 +0,0 @@
1
- require "nokogiri"
2
- require "htmlentities"
3
- require "lutaml/uml/has_attributes"
4
- require "lutaml/uml/document"
5
-
6
- module Lutaml
7
- module XMI
8
- module Parsers
9
- # Class for parsing .xmi schema files into ::Lutaml::Uml::Document
10
- class XML
11
- LOWER_VALUE_MAPPINGS = {
12
- "0" => "C",
13
- "1" => "M",
14
- }.freeze
15
- attr_reader :main_model, :xmi_cache
16
-
17
- # @param [String] io - file object with path to .xmi file
18
- # [Hash] options - options for parsing
19
- #
20
- # @return [Lutaml::XMI::Model::Document]
21
- def self.parse(io, _options = {})
22
- new.parse(Nokogiri::XML(io.read))
23
- end
24
-
25
- def parse(xmi_doc)
26
- @xmi_cache = {}
27
- @main_model = xmi_doc
28
- ::Lutaml::Uml::Document
29
- .new(serialize_to_hash(xmi_doc))
30
- end
31
-
32
- private
33
-
34
- def serialize_to_hash(xmi_doc)
35
- model = xmi_doc.xpath('//uml:Model[@xmi:type="uml:Model"]').first
36
- {
37
- name: model["name"],
38
- packages: serialize_model_packages(model)
39
- }
40
- end
41
-
42
- def serialize_model_packages(model)
43
- model.xpath('./packagedElement[@xmi:type="uml:Package"]').map do |package|
44
- {
45
- xmi_id: package["xmi:id"],
46
- name: package["name"],
47
- classes: serialize_model_classes(package),
48
- enums: serialize_model_enums(package),
49
- data_types: serialize_model_data_types(package),
50
- diagrams: serialize_model_diagrams(package),
51
- packages: serialize_model_packages(package),
52
- definition: doc_node_attribute_value(package, "documentation"),
53
- stereotype: doc_node_attribute_value(package, "stereotype")
54
- }
55
- end
56
- end
57
-
58
- def serialize_model_classes(model)
59
- model.xpath('./packagedElement[@xmi:type="uml:Class" or @xmi:type="uml:AssociationClass"]').map do |klass|
60
- {
61
- xmi_id: klass["xmi:id"],
62
- xmi_uuid: klass["xmi:uuid"],
63
- name: klass["name"],
64
- package: model,
65
- attributes: serialize_class_attributes(klass),
66
- associations: serialize_model_associations(klass),
67
- operations: serialize_class_operations(klass),
68
- constraints: serialize_class_constraints(klass),
69
- is_abstract: doc_node_attribute_value(klass, "isAbstract"),
70
- definition: doc_node_attribute_value(klass, "documentation"),
71
- stereotype: doc_node_attribute_value(klass, "stereotype")
72
- }
73
- end
74
- end
75
-
76
- def serialize_model_enums(model)
77
- model.xpath('./packagedElement[@xmi:type="uml:Enumeration"]').map do |enum|
78
- attributes = enum
79
- .xpath('.//ownedLiteral[@xmi:type="uml:EnumerationLiteral"]')
80
- .map do |value|
81
- type = value.xpath(".//type").first || {}
82
- {
83
- name: value["name"],
84
- type: lookup_entity_name(type["xmi:idref"]) || type["xmi:idref"],
85
- definition: lookup_attribute_definition(value),
86
- }
87
- end
88
- {
89
- xmi_id: enum["xmi:id"],
90
- xmi_uuid: enum["xmi:uuid"],
91
- name: enum["name"],
92
- values: attributes,
93
- definition: doc_node_attribute_value(enum, "documentation"),
94
- stereotype: doc_node_attribute_value(enum, "stereotype"),
95
- }
96
- end
97
- end
98
-
99
- def serialize_model_data_types(model)
100
- model.xpath('./packagedElement[@xmi:type="uml:DataType"]').map do |klass|
101
- {
102
- xmi_id: klass["xmi:id"],
103
- xmi_uuid: klass["xmi:uuid"],
104
- name: klass["name"],
105
- attributes: serialize_class_attributes(klass),
106
- operations: serialize_class_operations(klass),
107
- associations: serialize_model_associations(klass),
108
- constraints: serialize_class_constraints(klass),
109
- is_abstract: doc_node_attribute_value(klass, "isAbstract"),
110
- definition: doc_node_attribute_value(klass, "documentation"),
111
- stereotype: doc_node_attribute_value(klass, "stereotype"),
112
- }
113
- end
114
- end
115
-
116
- def serialize_model_diagrams(node)
117
- main_model.xpath(%(//diagrams/diagram/model[@package="#{node['xmi:id']}"])).map do |diagram_model|
118
- diagram = diagram_model.parent
119
- properties = diagram.children.find {|n| n.name == 'properties' }
120
- {
121
- xmi_id: diagram["xmi:id"],
122
- name: properties["name"],
123
- definition: properties.attributes['documentation']&.value
124
- }
125
- end
126
- end
127
-
128
- def serialize_model_associations(klass)
129
- xmi_id = klass["xmi:id"]
130
- main_model.xpath(%(//element[@xmi:idref="#{xmi_id}"]/links/*)).map do |link|
131
- link_member_name = link.attributes["start"].value == xmi_id ? "end" : "start"
132
- linke_owner_name = link_member_name == "start" ? "end" : "start"
133
- member_end, member_end_type, member_end_cardinality, member_end_attribute_name, member_end_xmi_id = serialize_member_type(xmi_id, link, link_member_name)
134
- owner_end, owner_end_cardinality, owner_end_attribute_name = serialize_owned_type(xmi_id, link, linke_owner_name)
135
- if member_end && ((member_end_type != 'aggregation') || (member_end_type == 'aggregation' && member_end_attribute_name))
136
- doc_node_name = link_member_name == "start" ? "source" : "target"
137
- definition_node = main_model.xpath(%(//connector[@xmi:idref="#{link['xmi:id']}"]/#{doc_node_name}/documentation)).first
138
- definition = definition_node.attributes['value']&.value if definition_node
139
- {
140
- xmi_id: link["xmi:id"],
141
- member_end: member_end,
142
- member_end_type: member_end_type,
143
- member_end_cardinality: member_end_cardinality,
144
- member_end_attribute_name: member_end_attribute_name,
145
- member_end_xmi_id: member_end_xmi_id,
146
- owner_end: owner_end,
147
- owner_end_xmi_id: xmi_id,
148
- definition: definition
149
- }
150
- end
151
- end.uniq
152
- end
153
-
154
- def serialize_class_operations(klass)
155
- klass.xpath('.//ownedOperation').map do |attribute|
156
- type = attribute.xpath(".//type").first || {}
157
- if attribute.attributes["association"].nil?
158
- {
159
- # TODO: xmi_id
160
- # xmi_id: klass['xmi:id'],
161
- name: attribute["name"],
162
- definition: lookup_attribute_definition(attribute),
163
- }
164
- end
165
- end.compact
166
- end
167
-
168
- def serialize_class_constraints(klass)
169
- class_element_metadata(klass).xpath("./constraints/constraint").map do |constraint|
170
- {
171
- xmi_id: constraint["xmi:id"],
172
- body: constraint["name"],
173
- definition: HTMLEntities.new.decode(constraint["description"])
174
- }
175
- end
176
- end
177
-
178
- def serialize_owned_type(owner_xmi_id, link, linke_owner_name)
179
- return if link.name == 'NoteLink'
180
- return generalization_association(owner_xmi_id, link) if link.name == "Generalization"
181
-
182
- xmi_id = link.attributes[linke_owner_name].value
183
- owner_end = lookup_entity_name(xmi_id) || connector_source_name(xmi_id)
184
-
185
- if link.name == "Association"
186
- assoc_connector = main_model.xpath(%(//connector[@xmi:idref="#{link['xmi:id']}"]/source)).first
187
- if assoc_connector
188
- connector_type = assoc_connector.children.find { |node| node.name == 'type' }
189
- if connector_type && connector_type.attributes['multiplicity']
190
- cardinality = connector_type.attributes['multiplicity']&.value&.split('..')
191
- cardinality.unshift('1') if cardinality.length == 1
192
- min, max = cardinality
193
- end
194
- connector_role = assoc_connector.children.find { |node| node.name == 'role' }
195
- if connector_role
196
- owned_attribute_name = connector_role.attributes["name"]&.value
197
- end
198
- owned_cardinality = { "min" => LOWER_VALUE_MAPPINGS[min], "max" => max }
199
- end
200
- else
201
- owned_node = main_model.xpath(%(//ownedAttribute[@association]/type[@xmi:idref="#{xmi_id}"])).first
202
- if owned_node
203
- assoc = owned_node.parent
204
- owned_cardinality = { "min" => cardinality_min_value(assoc), "max" => cardinality_max_value(assoc) }
205
- owned_attribute_name = assoc.attributes["name"]&.value
206
- end
207
- end
208
-
209
- [owner_end, owned_cardinality, owned_attribute_name]
210
- end
211
-
212
- def serialize_member_type(owner_xmi_id, link, link_member_name)
213
- return if link.name == 'NoteLink'
214
- return generalization_association(owner_xmi_id, link) if link.name == "Generalization"
215
-
216
- xmi_id = link.attributes[link_member_name].value
217
- if link.attributes["start"].value == owner_xmi_id
218
- xmi_id = link.attributes["end"].value
219
- member_end = lookup_entity_name(xmi_id) || connector_target_name(xmi_id)
220
- else
221
- xmi_id = link.attributes["start"].value
222
- member_end = lookup_entity_name(xmi_id) || connector_source_name(xmi_id)
223
- end
224
-
225
- if link.name == "Association"
226
- connector_type = link_member_name == "start" ? "source" : "target"
227
- assoc_connector = main_model.xpath(%(//connector[@xmi:idref="#{link['xmi:id']}"]/#{connector_type})).first
228
- if assoc_connector
229
- connector_type = assoc_connector.children.find { |node| node.name == 'type' }
230
- if connector_type && connector_type.attributes['multiplicity']
231
- cardinality = connector_type.attributes['multiplicity']&.value&.split('..')
232
- cardinality.unshift('1') if cardinality.length == 1
233
- min, max = cardinality
234
- end
235
- connector_role = assoc_connector.children.find { |node| node.name == 'role' }
236
- if connector_role
237
- member_end_attribute_name = connector_role.attributes["name"]&.value
238
- end
239
- member_end_cardinality = { "min" => LOWER_VALUE_MAPPINGS[min], "max" => max }
240
- end
241
- else
242
- member_end_node = main_model.xpath(%(//ownedAttribute[@association]/type[@xmi:idref="#{xmi_id}"])).first
243
- if member_end_node
244
- assoc = member_end_node.parent
245
- member_end_cardinality = { "min" => cardinality_min_value(assoc), "max" => cardinality_max_value(assoc) }
246
- member_end_attribute_name = assoc.attributes["name"]&.value
247
- end
248
- end
249
-
250
- [member_end, "aggregation", member_end_cardinality, member_end_attribute_name, xmi_id]
251
- end
252
-
253
- def generalization_association(owner_xmi_id, link)
254
- if link.attributes["start"].value == owner_xmi_id
255
- xmi_id = link.attributes["end"].value
256
- member_end_type = "inheritance"
257
- member_end = lookup_entity_name(xmi_id) || connector_target_name(xmi_id)
258
- else
259
- xmi_id = link.attributes["start"].value
260
- member_end_type = "generalization"
261
- member_end = lookup_entity_name(xmi_id) || connector_source_name(xmi_id)
262
- end
263
-
264
- member_end_node = main_model.xpath(%(//ownedAttribute[@association]/type[@xmi:idref="#{xmi_id}"])).first
265
- if member_end_node
266
- assoc = member_end_node.parent
267
- member_end_cardinality = { "min" => cardinality_min_value(assoc), "max" => cardinality_max_value(assoc) }
268
- end
269
-
270
- [member_end, member_end_type, member_end_cardinality, nil, xmi_id]
271
- end
272
-
273
- def class_element_metadata(klass)
274
- main_model.xpath(%(//element[@xmi:idref="#{klass['xmi:id']}"]))
275
- end
276
-
277
- def serialize_class_attributes(klass)
278
- klass.xpath('.//ownedAttribute[@xmi:type="uml:Property"]').map do |attribute|
279
- type = attribute.xpath(".//type").first || {}
280
- if attribute.attributes["association"].nil?
281
- {
282
- # TODO: xmi_id
283
- # xmi_id: klass['xmi:id'],
284
- name: attribute["name"],
285
- type: lookup_entity_name(type["xmi:idref"]) || type["xmi:idref"],
286
- xmi_id: type["xmi:idref"],
287
- is_derived: attribute["isDerived"],
288
- cardinality: { "min" => cardinality_min_value(attribute), "max" => cardinality_max_value(attribute) },
289
- definition: lookup_attribute_definition(attribute),
290
- }
291
- end
292
- end.compact
293
- end
294
-
295
- def cardinality_min_value(node)
296
- lower_value_node = node.xpath(".//lowerValue").first
297
- return unless lower_value_node
298
-
299
- lower_value = lower_value_node.attributes["value"]&.value
300
- LOWER_VALUE_MAPPINGS[lower_value]
301
- end
302
-
303
- def cardinality_max_value(node)
304
- upper_value_node = node.xpath(".//upperValue").first
305
- return unless upper_value_node
306
-
307
- upper_value_node.attributes["value"]&.value
308
- end
309
-
310
- def doc_node_attribute_value(node, attr_name)
311
- xmi_id = node["xmi:id"]
312
- doc_node = main_model.xpath(%(//element[@xmi:idref="#{xmi_id}"]/properties)).first
313
- return unless doc_node
314
-
315
- doc_node.attributes[attr_name]&.value
316
- end
317
-
318
- def lookup_attribute_definition(node)
319
- xmi_id = node["xmi:id"]
320
- doc_node = main_model.xpath(%(//attribute[@xmi:idref="#{xmi_id}"]/documentation)).first
321
- return unless doc_node
322
-
323
- doc_node.attributes["value"]&.value
324
- end
325
-
326
- def lookup_entity_name(xmi_id)
327
- xmi_cache[xmi_id] ||= model_node_name_by_xmi_id(xmi_id)
328
- xmi_cache[xmi_id]
329
- end
330
-
331
- def connector_source_name(xmi_id)
332
- node = main_model.xpath(%(//source[@xmi:idref="#{xmi_id}"]/model)).first
333
- return unless node
334
-
335
- node.attributes["name"]&.value
336
- end
337
-
338
- def connector_target_name(xmi_id)
339
- node = main_model.xpath(%(//target[@xmi:idref="#{xmi_id}"]/model)).first
340
- return unless node
341
-
342
- node.attributes["name"]&.value
343
- end
344
-
345
- def model_node_name_by_xmi_id(xmi_id)
346
- node = main_model.xpath(%(//*[@xmi:id="#{xmi_id}"])).first
347
- return unless node
348
-
349
- node.attributes["name"]&.value
350
- end
351
- end
352
- end
353
- end
354
- end
@@ -1,5 +0,0 @@
1
- module Lutaml
2
- module XMI
3
- VERSION = "0.2.3"
4
- end
5
- end
data/lib/lutaml/xmi.rb DELETED
@@ -1,7 +0,0 @@
1
- require "lutaml/xmi/version"
2
- require "lutaml/xmi/parsers/xml"
3
-
4
- module Lutaml
5
- module XMI
6
- end
7
- end