lutaml-xmi 0.2.2 → 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,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