lutaml-xmi 0.2.3 → 1.0.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.
@@ -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