lutaml-xmi 0.2.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,351 +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 = 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
- owner_end: owner_end,
146
- definition: definition
147
- }
148
- end
149
- end.uniq
150
- end
151
-
152
- def serialize_class_operations(klass)
153
- klass.xpath('.//ownedOperation').map do |attribute|
154
- type = attribute.xpath(".//type").first || {}
155
- if attribute.attributes["association"].nil?
156
- {
157
- # TODO: xmi_id
158
- # xmi_id: klass['xmi:id'],
159
- name: attribute["name"],
160
- definition: lookup_attribute_definition(attribute),
161
- }
162
- end
163
- end.compact
164
- end
165
-
166
- def serialize_class_constraints(klass)
167
- class_element_metadata(klass).xpath("./constraints/constraint").map do |constraint|
168
- {
169
- xmi_id: constraint["xmi:id"],
170
- body: constraint["name"],
171
- definition: HTMLEntities.new.decode(constraint["description"])
172
- }
173
- end
174
- end
175
-
176
- def serialize_owned_type(owner_xmi_id, link, linke_owner_name)
177
- return if link.name == 'NoteLink'
178
- return generalization_association(owner_xmi_id, link) if link.name == "Generalization"
179
-
180
- xmi_id = link.attributes[linke_owner_name].value
181
- owner_end = lookup_entity_name(xmi_id) || connector_source_name(xmi_id)
182
-
183
- if link.name == "Association"
184
- assoc_connector = main_model.xpath(%(//connector[@xmi:idref="#{link['xmi:id']}"]/source)).first
185
- if assoc_connector
186
- connector_type = assoc_connector.children.find { |node| node.name == 'type' }
187
- if connector_type && connector_type.attributes['multiplicity']
188
- cardinality = connector_type.attributes['multiplicity']&.value&.split('..')
189
- cardinality.unshift('1') if cardinality.length == 1
190
- min, max = cardinality
191
- end
192
- connector_role = assoc_connector.children.find { |node| node.name == 'role' }
193
- if connector_role
194
- owned_attribute_name = connector_role.attributes["name"]&.value
195
- end
196
- owned_cardinality = { "min" => LOWER_VALUE_MAPPINGS[min], "max" => max }
197
- end
198
- else
199
- owned_node = main_model.xpath(%(//ownedAttribute[@association]/type[@xmi:idref="#{xmi_id}"])).first
200
- if owned_node
201
- assoc = owned_node.parent
202
- owned_cardinality = { "min" => cardinality_min_value(assoc), "max" => cardinality_max_value(assoc) }
203
- owned_attribute_name = assoc.attributes["name"]&.value
204
- end
205
- end
206
-
207
- [owner_end, owned_cardinality, owned_attribute_name]
208
- end
209
-
210
- def serialize_member_type(owner_xmi_id, link, link_member_name)
211
- return if link.name == 'NoteLink'
212
- return generalization_association(owner_xmi_id, link) if link.name == "Generalization"
213
-
214
- xmi_id = link.attributes[link_member_name].value
215
- if link.attributes["start"].value == owner_xmi_id
216
- xmi_id = link.attributes["end"].value
217
- member_end = lookup_entity_name(xmi_id) || connector_target_name(xmi_id)
218
- else
219
- xmi_id = link.attributes["start"].value
220
- member_end = lookup_entity_name(xmi_id) || connector_source_name(xmi_id)
221
- end
222
-
223
- if link.name == "Association"
224
- connector_type = link_member_name == "start" ? "source" : "target"
225
- assoc_connector = main_model.xpath(%(//connector[@xmi:idref="#{link['xmi:id']}"]/#{connector_type})).first
226
- if assoc_connector
227
- connector_type = assoc_connector.children.find { |node| node.name == 'type' }
228
- if connector_type && connector_type.attributes['multiplicity']
229
- cardinality = connector_type.attributes['multiplicity']&.value&.split('..')
230
- cardinality.unshift('1') if cardinality.length == 1
231
- min, max = cardinality
232
- end
233
- connector_role = assoc_connector.children.find { |node| node.name == 'role' }
234
- if connector_role
235
- member_end_attribute_name = connector_role.attributes["name"]&.value
236
- end
237
- member_end_cardinality = { "min" => LOWER_VALUE_MAPPINGS[min], "max" => max }
238
- end
239
- else
240
- member_end_node = main_model.xpath(%(//ownedAttribute[@association]/type[@xmi:idref="#{xmi_id}"])).first
241
- if member_end_node
242
- assoc = member_end_node.parent
243
- member_end_cardinality = { "min" => cardinality_min_value(assoc), "max" => cardinality_max_value(assoc) }
244
- member_end_attribute_name = assoc.attributes["name"]&.value
245
- end
246
- end
247
-
248
- [member_end, "aggregation", member_end_cardinality, member_end_attribute_name]
249
- end
250
-
251
- def generalization_association(owner_xmi_id, link)
252
- if link.attributes["start"].value == owner_xmi_id
253
- xmi_id = link.attributes["end"].value
254
- member_end_type = "inheritance"
255
- member_end = lookup_entity_name(xmi_id) || connector_target_name(xmi_id)
256
- else
257
- xmi_id = link.attributes["start"].value
258
- member_end_type = "generalization"
259
- member_end = lookup_entity_name(xmi_id) || connector_source_name(xmi_id)
260
- end
261
-
262
- member_end_node = main_model.xpath(%(//ownedAttribute[@association]/type[@xmi:idref="#{xmi_id}"])).first
263
- if member_end_node
264
- assoc = member_end_node.parent
265
- member_end_cardinality = { "min" => cardinality_min_value(assoc), "max" => cardinality_max_value(assoc) }
266
- end
267
-
268
- [member_end, member_end_type, member_end_cardinality, nil]
269
- end
270
-
271
- def class_element_metadata(klass)
272
- main_model.xpath(%(//element[@xmi:idref="#{klass['xmi:id']}"]))
273
- end
274
-
275
- def serialize_class_attributes(klass)
276
- klass.xpath('.//ownedAttribute[@xmi:type="uml:Property"]').map do |attribute|
277
- type = attribute.xpath(".//type").first || {}
278
- if attribute.attributes["association"].nil?
279
- {
280
- # TODO: xmi_id
281
- # xmi_id: klass['xmi:id'],
282
- name: attribute["name"],
283
- type: lookup_entity_name(type["xmi:idref"]) || type["xmi:idref"],
284
- is_derived: attribute["isDerived"],
285
- cardinality: { "min" => cardinality_min_value(attribute), "max" => cardinality_max_value(attribute) },
286
- definition: lookup_attribute_definition(attribute),
287
- }
288
- end
289
- end.compact
290
- end
291
-
292
- def cardinality_min_value(node)
293
- lower_value_node = node.xpath(".//lowerValue").first
294
- return unless lower_value_node
295
-
296
- lower_value = lower_value_node.attributes["value"]&.value
297
- LOWER_VALUE_MAPPINGS[lower_value]
298
- end
299
-
300
- def cardinality_max_value(node)
301
- upper_value_node = node.xpath(".//upperValue").first
302
- return unless upper_value_node
303
-
304
- upper_value_node.attributes["value"]&.value
305
- end
306
-
307
- def doc_node_attribute_value(node, attr_name)
308
- xmi_id = node["xmi:id"]
309
- doc_node = main_model.xpath(%(//element[@xmi:idref="#{xmi_id}"]/properties)).first
310
- return unless doc_node
311
-
312
- doc_node.attributes[attr_name]&.value
313
- end
314
-
315
- def lookup_attribute_definition(node)
316
- xmi_id = node["xmi:id"]
317
- doc_node = main_model.xpath(%(//attribute[@xmi:idref="#{xmi_id}"]/documentation)).first
318
- return unless doc_node
319
-
320
- doc_node.attributes["value"]&.value
321
- end
322
-
323
- def lookup_entity_name(xmi_id)
324
- xmi_cache[xmi_id] ||= model_node_name_by_xmi_id(xmi_id)
325
- xmi_cache[xmi_id]
326
- end
327
-
328
- def connector_source_name(xmi_id)
329
- node = main_model.xpath(%(//source[@xmi:idref="#{xmi_id}"]/model)).first
330
- return unless node
331
-
332
- node.attributes["name"]&.value
333
- end
334
-
335
- def connector_target_name(xmi_id)
336
- node = main_model.xpath(%(//target[@xmi:idref="#{xmi_id}"]/model)).first
337
- return unless node
338
-
339
- node.attributes["name"]&.value
340
- end
341
-
342
- def model_node_name_by_xmi_id(xmi_id)
343
- node = main_model.xpath(%(//*[@xmi:id="#{xmi_id}"])).first
344
- return unless node
345
-
346
- node.attributes["name"]&.value
347
- end
348
- end
349
- end
350
- end
351
- end
@@ -1,5 +0,0 @@
1
- module Lutaml
2
- module XMI
3
- VERSION = "0.2.2"
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