lutaml 0.9.28 → 0.9.29

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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/bin/plantuml2lutaml +11 -7
  3. data/bin/yaml2lutaml +1 -1
  4. data/exe/lutaml-sysml +4 -2
  5. data/exe/lutaml-wsd2uml +11 -7
  6. data/exe/lutaml-yaml2uml +1 -1
  7. data/lib/lutaml/express/parsers/exp.rb +4 -4
  8. data/lib/lutaml/formatter/graphviz.rb +7 -7
  9. data/lib/lutaml/sysml/allocate.rb +6 -7
  10. data/lib/lutaml/sysml/allocated.rb +6 -6
  11. data/lib/lutaml/sysml/binding_connector.rb +6 -6
  12. data/lib/lutaml/sysml/block.rb +28 -25
  13. data/lib/lutaml/sysml/constraint_block.rb +11 -11
  14. data/lib/lutaml/sysml/copy.rb +5 -5
  15. data/lib/lutaml/sysml/derive_requirement.rb +6 -6
  16. data/lib/lutaml/sysml/nested_connector_end.rb +9 -9
  17. data/lib/lutaml/sysml/refine.rb +6 -6
  18. data/lib/lutaml/sysml/requirement_related.rb +6 -6
  19. data/lib/lutaml/sysml/satisfy.rb +6 -6
  20. data/lib/lutaml/sysml/test_case.rb +20 -19
  21. data/lib/lutaml/sysml/trace.rb +6 -6
  22. data/lib/lutaml/sysml/verify.rb +5 -5
  23. data/lib/lutaml/sysml/version.rb +1 -1
  24. data/lib/lutaml/sysml/xmi_file.rb +455 -415
  25. data/lib/lutaml/sysml.rb +1 -1
  26. data/lib/lutaml/uml/association.rb +4 -3
  27. data/lib/lutaml/uml/data_type.rb +1 -0
  28. data/lib/lutaml/uml/document.rb +4 -1
  29. data/lib/lutaml/uml/formatter/graphviz.rb +11 -13
  30. data/lib/lutaml/uml/has_attributes.rb +2 -2
  31. data/lib/lutaml/uml/has_members.rb +4 -3
  32. data/lib/lutaml/uml/node/class_node.rb +5 -7
  33. data/lib/lutaml/uml/node/field.rb +1 -3
  34. data/lib/lutaml/uml/node/method.rb +1 -3
  35. data/lib/lutaml/uml/node/relationship.rb +1 -3
  36. data/lib/lutaml/uml/operation.rb +6 -6
  37. data/lib/lutaml/uml/package.rb +3 -1
  38. data/lib/lutaml/uml/parsers/attribute.rb +1 -3
  39. data/lib/lutaml/uml/parsers/dsl.rb +11 -10
  40. data/lib/lutaml/uml/parsers/dsl_preprocessor.rb +7 -6
  41. data/lib/lutaml/uml/parsers/yaml.rb +2 -2
  42. data/lib/lutaml/uml/serializers/class.rb +1 -1
  43. data/lib/lutaml/uml/top_element.rb +9 -9
  44. data/lib/lutaml/uml/top_element_attribute.rb +6 -6
  45. data/lib/lutaml/uml/value.rb +6 -6
  46. data/lib/lutaml/version.rb +1 -1
  47. data/lib/lutaml/xmi/liquid_drops/association_drop.rb +31 -11
  48. data/lib/lutaml/xmi/liquid_drops/attribute_drop.rb +29 -11
  49. data/lib/lutaml/xmi/liquid_drops/cardinality_drop.rb +8 -2
  50. data/lib/lutaml/xmi/liquid_drops/constraint_drop.rb +6 -4
  51. data/lib/lutaml/xmi/liquid_drops/data_type_drop.rb +76 -18
  52. data/lib/lutaml/xmi/liquid_drops/diagram_drop.rb +13 -6
  53. data/lib/lutaml/xmi/liquid_drops/enum_drop.rb +16 -7
  54. data/lib/lutaml/xmi/liquid_drops/enum_owned_literal_drop.rb +10 -4
  55. data/lib/lutaml/xmi/liquid_drops/generalization_attribute_drop.rb +2 -0
  56. data/lib/lutaml/xmi/liquid_drops/generalization_drop.rb +10 -3
  57. data/lib/lutaml/xmi/liquid_drops/klass_drop.rb +98 -24
  58. data/lib/lutaml/xmi/liquid_drops/operation_drop.rb +11 -5
  59. data/lib/lutaml/xmi/liquid_drops/package_drop.rb +61 -18
  60. data/lib/lutaml/xmi/liquid_drops/root_drop.rb +14 -4
  61. data/lib/lutaml/xmi/parsers/xmi_base.rb +1031 -0
  62. data/lib/lutaml/xmi/parsers/xml.rb +19 -1016
  63. data/lib/lutaml/xml/parsers/xml.rb +2 -2
  64. metadata +4 -3
@@ -0,0 +1,1031 @@
1
+ require "nokogiri"
2
+ require "htmlentities"
3
+ require "lutaml/uml/has_attributes"
4
+ require "lutaml/uml/document"
5
+ require "lutaml/xmi"
6
+ require "xmi"
7
+
8
+ module Lutaml
9
+ module XMI
10
+ module Parsers
11
+ module XMIBase
12
+ def self.included(base)
13
+ base.extend(ClassMethods)
14
+ end
15
+
16
+ module ClassMethods
17
+ private
18
+
19
+ # @param xml [String]
20
+ # @return [Lutaml::Model::Serializable]
21
+ def get_xmi_model(xml)
22
+ Xmi::Sparx::SparxRoot.parse_xml(File.read(xml))
23
+ end
24
+ end
25
+
26
+ # @param xmi_model [Lutaml::Model::Serializable]
27
+ def set_xmi_model(xmi_model)
28
+ @xmi_cache = {}
29
+ @xmi_root_model = xmi_model
30
+ map_id_name(@xmi_cache, @xmi_root_model)
31
+ end
32
+
33
+ # @param yaml [String]
34
+ # @return [Hash]
35
+ def get_guidance(yaml)
36
+ return unless yaml
37
+
38
+ YAML.safe_load(File.read(yaml, encoding: "UTF-8"))
39
+ end
40
+
41
+ private
42
+
43
+ # @param xmi_model [Lutaml::Model::Serializable]
44
+ # @param with_gen: [Boolean]
45
+ # @param with_absolute_path: [Boolean]
46
+ # @return [Hash]
47
+ # @note xpath: //uml:Model[@xmi:type="uml:Model"]
48
+ def serialize_to_hash(xmi_model,
49
+ with_gen: false, with_absolute_path: false)
50
+ model = xmi_model.model
51
+ {
52
+ name: model.name,
53
+ packages: serialize_model_packages(
54
+ model,
55
+ with_gen: with_gen,
56
+ with_absolute_path: with_absolute_path,
57
+ ),
58
+ }
59
+ end
60
+
61
+ # @param model [Lutaml::Model::Serializable]
62
+ # @param with_gen: [Boolean]
63
+ # @param with_absolute_path: [Boolean]
64
+ # @param absolute_path: [String]
65
+ # @return [Array<Hash>]
66
+ # @note xpath ./packagedElement[@xmi:type="uml:Package"]
67
+ def serialize_model_packages(model, # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
68
+ with_gen: false, with_absolute_path: false, absolute_path: "")
69
+ packages = model.packaged_element.select do |e|
70
+ e.type?("uml:Package")
71
+ end
72
+
73
+ if with_absolute_path
74
+ absolute_path = "#{absolute_path}::#{model.name}"
75
+ end
76
+
77
+ packages.map do |package| # rubocop:disable Metrics/BlockLength
78
+ h = {
79
+ xmi_id: package.id,
80
+ name: get_package_name(package),
81
+ classes: serialize_model_classes(
82
+ package, model,
83
+ with_gen: with_gen,
84
+ with_absolute_path: with_absolute_path,
85
+ absolute_path: "#{absolute_path}::#{package.name}"
86
+ ),
87
+ enums: serialize_model_enums(package),
88
+ data_types: serialize_model_data_types(package),
89
+ diagrams: serialize_model_diagrams(
90
+ package.id,
91
+ with_package: with_gen,
92
+ ),
93
+ packages: serialize_model_packages(
94
+ package,
95
+ with_gen: with_gen,
96
+ with_absolute_path: with_absolute_path,
97
+ absolute_path: absolute_path,
98
+ ),
99
+ definition: doc_node_attribute_value(package.id, "documentation"),
100
+ stereotype: doc_node_attribute_value(package.id, "stereotype"),
101
+ }
102
+
103
+ if with_absolute_path
104
+ h[:absolute_path] = "#{absolute_path}::#{package.name}"
105
+ end
106
+
107
+ h
108
+ end
109
+ end
110
+
111
+ # @param package [Lutaml::Model::Serializable]
112
+ # @return [String]
113
+ def get_package_name(package) # rubocop:disable Metrics/AbcSize
114
+ return package.name unless package.name.nil?
115
+
116
+ connector = fetch_connector(package.id)
117
+ if connector.target&.model && connector.target.model&.name
118
+ return "#{connector.target.model.name} " \
119
+ "(#{package.type.split(':').last})"
120
+ end
121
+
122
+ "unnamed"
123
+ end
124
+
125
+ # @param package [Lutaml::Model::Serializable]
126
+ # @param model [Lutaml::Model::Serializable]
127
+ # @param with_gen: [Boolean]
128
+ # @param with_absolute_path: [Boolean]
129
+ # @return [Array<Hash>]
130
+ # @note xpath ./packagedElement[@xmi:type="uml:Class" or
131
+ # @xmi:type="uml:AssociationClass"]
132
+ def serialize_model_classes(package, model, # rubocop:disable Metrics/MethodLength
133
+ with_gen: false, with_absolute_path: false, absolute_path: "")
134
+ klasses = package.packaged_element.select do |e|
135
+ e.type?("uml:Class") || e.type?("uml:AssociationClass") ||
136
+ e.type?("uml:Interface")
137
+ end
138
+
139
+ klasses.map do |klass|
140
+ h = build_klass_hash(
141
+ klass, model,
142
+ with_gen: with_gen
143
+ )
144
+
145
+ h[:absolute_path] = absolute_path if with_absolute_path
146
+
147
+ h
148
+ end
149
+ end
150
+
151
+ # @param klass [Lutaml::Model::Serializable]
152
+ # @param model [Lutaml::Model::Serializable]
153
+ # @param with_gen: [Boolean]
154
+ # @param with_absolute_path: [Boolean]
155
+ # @return [Hash]
156
+ def build_klass_hash(klass, model, # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
157
+ with_gen: false, with_absolute_path: false, absolute_path: "")
158
+ klass_hash = {
159
+ xmi_id: klass.id,
160
+ name: klass.name,
161
+ package: model,
162
+ type: klass.type.split(":").last,
163
+ attributes: serialize_class_attributes(klass),
164
+ associations: serialize_model_associations(klass.id),
165
+ operations: serialize_class_operations(klass),
166
+ constraints: serialize_class_constraints(klass.id),
167
+ is_abstract: doc_node_attribute_value(klass.id, "isAbstract"),
168
+ definition: doc_node_attribute_value(klass.id, "documentation"),
169
+ stereotype: doc_node_attribute_value(klass.id, "stereotype"),
170
+ }
171
+
172
+ klass_hash[:absolute_path] = absolute_path if with_absolute_path
173
+
174
+ if with_gen && klass.type?("uml:Class")
175
+ klass_hash[:generalization] = serialize_generalization(klass)
176
+ end
177
+
178
+ klass_hash
179
+ end
180
+
181
+ # @param klass [Lutaml::Model::Serializable]
182
+ # # @return [Hash]
183
+ def serialize_generalization(klass)
184
+ general_hash, next_general_node_id = get_top_level_general_hash(klass)
185
+ return general_hash unless next_general_node_id
186
+
187
+ general_hash[:general] = serialize_generalization_attributes(
188
+ next_general_node_id,
189
+ )
190
+
191
+ general_hash
192
+ end
193
+
194
+ # @param klass [Lutaml::Model::Serializable]
195
+ # @return [Array<Hash>]
196
+ def get_top_level_general_hash(klass) # rubocop:disable Metrics/AbcSize
197
+ general_hash, next_general_node_id = get_general_hash(klass.id)
198
+ general_hash[:name] = klass.name
199
+ general_hash[:type] = klass.type
200
+ general_hash[:definition] = lookup_general_documentation(klass.id)
201
+ general_hash[:stereotype] = doc_node_attribute_value(
202
+ klass.id, "stereotype"
203
+ )
204
+
205
+ # update_inherited_attributes(general_hash)
206
+ # update_gen_attributes(general_hash)
207
+
208
+ [general_hash, next_general_node_id]
209
+ end
210
+
211
+ def lookup_general_documentation(klass_id)
212
+ # lookup_attribute_documentation(klass_id) ||
213
+ # lookup_element_prop_documentation(klass_id)
214
+
215
+ lookup_element_prop_documentation(klass_id)
216
+ end
217
+
218
+ def update_gen_attributes(general_hash)
219
+ general_hash[:gen_attributes] = serialize_gen_attributes
220
+ end
221
+
222
+ def update_inherited_attributes(general_hash)
223
+ general_hash[:gml_attributes] = serialize_gml_attributes
224
+ general_hash[:core_attributes] = serialize_core_attributes
225
+ end
226
+
227
+ # @param xmi_id [String]
228
+ # @param model [Lutaml::Model::Serializable]
229
+ # @return [Array<Hash>]
230
+ # @note get generalization node and its owned attributes
231
+ def serialize_generalization_attributes(general_id)
232
+ general_hash, next_general_node_id = get_general_hash(general_id)
233
+
234
+ if next_general_node_id
235
+ general_hash[:general] = serialize_generalization_attributes(
236
+ next_general_node_id,
237
+ )
238
+ end
239
+
240
+ general_hash
241
+ end
242
+
243
+ # @param xmi_id [String]
244
+ # @return [Lutaml::Model::Serializable]
245
+ def get_general_node(xmi_id)
246
+ find_packaged_element_by_id(xmi_id)
247
+ end
248
+
249
+ # @param general_node [Lutaml::Model::Serializable]
250
+ # @return [Hash]
251
+ def get_general_attributes(general_node)
252
+ serialize_class_attributes(general_node, with_assoc: true)
253
+ end
254
+
255
+ # @param general_node [Lutaml::Model::Serializable]
256
+ # @return [String]
257
+ def get_next_general_node_id(general_node)
258
+ general_node.generalization.first&.general
259
+ end
260
+
261
+ # @param general_id [String]
262
+ # @return [Array<Hash>]
263
+ def get_general_hash(general_id)
264
+ general_node = get_general_node(general_id)
265
+ return [] unless general_node
266
+
267
+ general_node_attrs = get_general_attributes(general_node)
268
+ general_upper_klass = find_upper_level_packaged_element(general_id)
269
+ next_general_node_id = get_next_general_node_id(general_node)
270
+
271
+ [
272
+ {
273
+ general_id: general_id,
274
+ general_name: general_node.name,
275
+ general_attributes: general_node_attrs,
276
+ general_upper_klass: general_upper_klass,
277
+ general: {},
278
+ },
279
+ next_general_node_id,
280
+ ]
281
+ end
282
+
283
+ # @param id [String]
284
+ # @return [Lutaml::Model::Serializable]
285
+ def find_packaged_element_by_id(id)
286
+ all_packaged_elements.find { |e| e.id == id }
287
+ end
288
+
289
+ # @param id [String]
290
+ # @return [Lutaml::Model::Serializable]
291
+ def find_upper_level_packaged_element(klass_id)
292
+ upper_klass = all_packaged_elements.find do |e|
293
+ e.packaged_element.find { |pe| pe.id == klass_id }
294
+ end
295
+ upper_klass&.name
296
+ end
297
+
298
+ # @param path [String]
299
+ # @return [Lutaml::Model::Serializable]
300
+ def find_klass_packaged_element(path)
301
+ lutaml_path = Lutaml::Path.parse(path)
302
+ if lutaml_path.segments.count == 1
303
+ return find_klass_packaged_element_by_name(path)
304
+ end
305
+
306
+ find_klass_packaged_element_by_path(lutaml_path)
307
+ end
308
+
309
+ # @param path [Lutaml::Path::ElementPath]
310
+ # @return [Lutaml::Model::Serializable]
311
+ def find_klass_packaged_element_by_path(path)
312
+ if path.absolute?
313
+ iterate_packaged_element(
314
+ @xmi_root_model.model, path.segments.map(&:name)
315
+ )
316
+ else
317
+ iterate_relative_packaged_element(path.segments.map(&:name))
318
+ end
319
+ end
320
+
321
+ # @param name_array [Array<String>]
322
+ # @return [Lutaml::Model::Serializable]
323
+ def iterate_relative_packaged_element(name_array)
324
+ # match the first element in the name_array
325
+ matched_elements = all_packaged_elements.select do |e|
326
+ e.type?("uml:Package") && e.name == name_array[0]
327
+ end
328
+
329
+ # match the rest elements in the name_array
330
+ result = matched_elements.map do |e|
331
+ iterate_packaged_element(e, name_array, type: "uml:Class")
332
+ end
333
+
334
+ result.compact.first
335
+ end
336
+
337
+ # @param model [Lutaml::Model::Serializable]
338
+ # @param name_array [Array<String>]
339
+ # @param index: [Integer]
340
+ # @param type: [String]
341
+ # @return [Lutaml::Model::Serializable]
342
+ def iterate_packaged_element(model, name_array,
343
+ index: 1, type: "uml:Package")
344
+ return model if index == name_array.count
345
+
346
+ model = model.packaged_element.find do |p|
347
+ p.name == name_array[index] && p.type?(type)
348
+ end
349
+
350
+ return nil if model.nil?
351
+
352
+ index += 1
353
+ type = index == name_array.count - 1 ? "uml:Class" : "uml:Package"
354
+ iterate_packaged_element(model, name_array, index: index, type: type)
355
+ end
356
+
357
+ # @param name [String]
358
+ # @return [Lutaml::Model::Serializable]
359
+ def find_klass_packaged_element_by_name(name)
360
+ all_packaged_elements.find do |e|
361
+ e.type?("uml:Class") && e.name == name
362
+ end
363
+ end
364
+
365
+ # @param name [String]
366
+ # @return [Lutaml::Model::Serializable]
367
+ def find_packaged_element_by_name(name)
368
+ all_packaged_elements.find do |e|
369
+ e.name == name
370
+ end
371
+ end
372
+
373
+ # @param package [Lutaml::Model::Serializable]
374
+ # @return [Array<Hash>]
375
+ # @note xpath ./packagedElement[@xmi:type="uml:Enumeration"]
376
+ def serialize_model_enums(package)
377
+ package.packaged_element.select { |e| e.type?("uml:Enumeration") }
378
+ .map do |enum|
379
+ {
380
+ xmi_id: enum.id,
381
+ name: enum.name,
382
+ values: serialize_enum_owned_literal(enum),
383
+ definition: doc_node_attribute_value(enum.id, "documentation"),
384
+ stereotype: doc_node_attribute_value(enum.id, "stereotype"),
385
+ }
386
+ end
387
+ end
388
+
389
+ # @param model [Lutaml::Model::Serializable]
390
+ # @return [Hash]
391
+ # @note xpath .//ownedLiteral[@xmi:type="uml:EnumerationLiteral"]
392
+ def serialize_enum_owned_literal(enum) # rubocop:disable Metrics/MethodLength
393
+ owned_literals = enum.owned_literal.select do |owned_literal|
394
+ owned_literal.type? "uml:EnumerationLiteral"
395
+ end
396
+
397
+ owned_literals.map do |owned_literal|
398
+ # xpath .//type
399
+ uml_type_id = owned_literal&.uml_type&.idref
400
+
401
+ {
402
+ name: owned_literal.name,
403
+ type: lookup_entity_name(uml_type_id) || uml_type_id,
404
+ definition: lookup_attribute_documentation(owned_literal.id),
405
+ }
406
+ end
407
+ end
408
+
409
+ # @param model [Lutaml::Model::Serializable]
410
+ # @return [Array<Hash>]
411
+ # @note xpath ./packagedElement[@xmi:type="uml:DataType"]
412
+ def serialize_model_data_types(model) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
413
+ all_data_type_elements = []
414
+ select_all_packaged_elements(all_data_type_elements, model,
415
+ "uml:DataType")
416
+ all_data_type_elements.map do |klass|
417
+ {
418
+ xmi_id: klass.id,
419
+ name: klass.name,
420
+ attributes: serialize_class_attributes(klass),
421
+ operations: serialize_class_operations(klass),
422
+ associations: serialize_model_associations(klass.id),
423
+ constraints: serialize_class_constraints(klass.id),
424
+ is_abstract: doc_node_attribute_value(klass.id, "isAbstract"),
425
+ definition: doc_node_attribute_value(klass.id, "documentation"),
426
+ stereotype: doc_node_attribute_value(klass.id, "stereotype"),
427
+ }
428
+ end
429
+ end
430
+
431
+ # @param node_id [String]
432
+ # @return [Array<Hash>]
433
+ # @note xpath %(//diagrams/diagram/model[@package="#{node['xmi:id']}"])
434
+ def serialize_model_diagrams(node_id, with_package: false) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
435
+ diagrams = @xmi_root_model.extension.diagrams.diagram.select do |d|
436
+ d.model.package == node_id
437
+ end
438
+
439
+ diagrams.map do |diagram|
440
+ h = {
441
+ xmi_id: diagram.id,
442
+ name: diagram.properties.name,
443
+ definition: diagram.properties.documentation,
444
+ }
445
+
446
+ if with_package
447
+ package_id = diagram.model.package
448
+ h[:package_id] = package_id
449
+ h[:package_name] = find_packaged_element_by_id(package_id)&.name
450
+ end
451
+
452
+ h
453
+ end
454
+ end
455
+
456
+ # @param xmi_id [String]
457
+ # @return [Array<Hash>]
458
+ # @note xpath %(//element[@xmi:idref="#{xmi_id}"]/links/*)
459
+ def serialize_model_associations(xmi_id) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
460
+ matched_element = @xmi_root_model.extension.elements.element
461
+ .find { |e| e.idref == xmi_id }
462
+
463
+ return if !matched_element ||
464
+ !matched_element.links ||
465
+ matched_element.links.association.empty?
466
+
467
+ matched_element.links.association.map do |assoc|
468
+ link_member = assoc.start == xmi_id ? "end" : "start"
469
+ linke_owner_name = link_member == "start" ? "end" : "start"
470
+
471
+ member_end, member_end_type, member_end_cardinality,
472
+ member_end_attribute_name, member_end_xmi_id =
473
+ serialize_member_type(xmi_id, assoc, link_member)
474
+
475
+ owner_end = serialize_owned_type(xmi_id, assoc, linke_owner_name)
476
+
477
+ if member_end && ((member_end_type != "aggregation") ||
478
+ (member_end_type == "aggregation" && member_end_attribute_name))
479
+
480
+ doc_node_name = (link_member == "start" ? "source" : "target")
481
+ definition = fetch_definition_node_value(assoc.id, doc_node_name)
482
+
483
+ {
484
+ xmi_id: assoc.id,
485
+ member_end: member_end,
486
+ member_end_type: member_end_type,
487
+ member_end_cardinality: member_end_cardinality,
488
+ member_end_attribute_name: member_end_attribute_name,
489
+ member_end_xmi_id: member_end_xmi_id,
490
+ owner_end: owner_end,
491
+ owner_end_xmi_id: xmi_id,
492
+ definition: definition,
493
+ }
494
+ end
495
+ end
496
+ end
497
+
498
+ # @param link_id [String]
499
+ # @return [Lutaml::Model::Serializable]
500
+ # @note xpath %(//connector[@xmi:idref="#{link_id}"])
501
+ def fetch_connector(link_id)
502
+ @xmi_root_model.extension.connectors.connector.find do |con|
503
+ con.idref == link_id
504
+ end
505
+ end
506
+
507
+ # @param link_id [String]
508
+ # @param node_name [String] source or target
509
+ # @return [String]
510
+ # @note xpath
511
+ # %(//connector[@xmi:idref="#{link_id}"]/#{node_name}/documentation)
512
+ def fetch_definition_node_value(link_id, node_name)
513
+ connector_node = fetch_connector(link_id)
514
+ connector_node.send(node_name.to_sym).documentation
515
+ end
516
+
517
+ # @param klass [Lutaml::Model::Serializable]
518
+ # @return [Array<Hash>]
519
+ # @note xpath .//ownedOperation
520
+ def serialize_class_operations(klass) # rubocop:disable Metrics/MethodLength
521
+ klass.owned_operation.map do |operation|
522
+ uml_type = operation.uml_type.first
523
+ uml_type_idref = uml_type.idref if uml_type
524
+
525
+ if operation.association.nil?
526
+ {
527
+ id: operation.id,
528
+ xmi_id: uml_type_idref,
529
+ name: operation.name,
530
+ definition: lookup_attribute_documentation(operation.id),
531
+ }
532
+ end
533
+ end.compact
534
+ end
535
+
536
+ # @param klass_id [String]
537
+ # @return [Array<Hash>]
538
+ # @note xpath ./constraints/constraint
539
+ def serialize_class_constraints(klass_id) # rubocop:disable Metrics/MethodLength
540
+ connector_node = fetch_connector(klass_id)
541
+
542
+ if connector_node
543
+ # In ea-xmi-2.5.1, constraints are moved to source/target under
544
+ # connectors
545
+ constraints = %i[source target].map do |st|
546
+ connector_node.send(st).constraints.constraint
547
+ end.flatten
548
+
549
+ constraints.map do |constraint|
550
+ {
551
+ name: HTMLEntities.new.decode(constraint.name),
552
+ type: constraint.type,
553
+ weight: constraint.weight,
554
+ status: constraint.status,
555
+ }
556
+ end
557
+ end
558
+ end
559
+
560
+ # @param owner_xmi_id [String]
561
+ # @param link [Lutaml::Model::Serializable]
562
+ # @param link_member_name [String]
563
+ # @return [String]
564
+ def serialize_owned_type(owner_xmi_id, link, linke_owner_name)
565
+ case link.name
566
+ when "NoteLink"
567
+ return
568
+ when "Generalization"
569
+ return generalization_association(owner_xmi_id, link)
570
+ end
571
+
572
+ xmi_id = link.send(linke_owner_name.to_sym)
573
+ lookup_entity_name(xmi_id) || connector_source_name(xmi_id)
574
+
575
+ # not necessary
576
+ # if link.name == "Association"
577
+ # owned_cardinality, owned_attribute_name =
578
+ # fetch_assoc_connector(link.id, "source")
579
+ # else
580
+ # owned_cardinality, owned_attribute_name =
581
+ # fetch_owned_attribute_node(xmi_id)
582
+ # end
583
+ # [owner_end, owned_cardinality, owned_attribute_name]
584
+ # owner_end
585
+ end
586
+
587
+ # @param owner_xmi_id [String]
588
+ # @param link [Lutaml::Model::Serializable]
589
+ # @return [Array<String, String>]
590
+ def serialize_member_end(owner_xmi_id, link) # rubocop:disable Metrics/MethodLength
591
+ case link.name
592
+ when "NoteLink"
593
+ return
594
+ when "Generalization"
595
+ return generalization_association(owner_xmi_id, link)
596
+ end
597
+
598
+ xmi_id = link.start
599
+ source_or_target = :source
600
+
601
+ if link.start == owner_xmi_id
602
+ xmi_id = link.end
603
+ source_or_target = :target
604
+ end
605
+
606
+ member_end = member_end_name(xmi_id, source_or_target, link.name)
607
+ [member_end, xmi_id]
608
+ end
609
+
610
+ # @param xmi_id [String]
611
+ # @param source_or_target [Symbol]
612
+ # @return [String]
613
+ def member_end_name(xmi_id, source_or_target, link_name) # rubocop:disable Metrics/MethodLength
614
+ connector_label = connector_labels(xmi_id, source_or_target)
615
+ entity_name = lookup_entity_name(xmi_id)
616
+ connector_name = connector_name_by_source_or_target(
617
+ xmi_id, source_or_target
618
+ )
619
+
620
+ case link_name
621
+ when "Aggregation"
622
+ connector_label || entity_name || connector_name
623
+ else
624
+ entity_name || connector_name
625
+ end
626
+ end
627
+
628
+ # @param owner_xmi_id [String]
629
+ # @param link [Lutaml::Model::Serializable]
630
+ # @param link_member_name [String]
631
+ # @return [Array<String, String, Hash, String, String>]
632
+ def serialize_member_type(owner_xmi_id, link, link_member_name)
633
+ member_end, xmi_id = serialize_member_end(owner_xmi_id, link)
634
+
635
+ if link.name == "Association"
636
+ connector_type = link_member_name == "start" ? "source" : "target"
637
+ member_end_cardinality, member_end_attribute_name =
638
+ fetch_assoc_connector(link.id, connector_type)
639
+ else
640
+ member_end_cardinality, member_end_attribute_name =
641
+ fetch_owned_attribute_node(xmi_id)
642
+ end
643
+
644
+ [member_end, "aggregation", member_end_cardinality,
645
+ member_end_attribute_name, xmi_id]
646
+ end
647
+
648
+ # @param link_id [String]
649
+ # @param connector_type [String]
650
+ # @return [Array<Hash, String>]
651
+ # @note xpath %(//connector[@xmi:idref="#{link_id}"]/#{connector_type})
652
+ def fetch_assoc_connector(link_id, connector_type) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
653
+ assoc_connector = fetch_connector(link_id).send(connector_type.to_sym)
654
+
655
+ if assoc_connector
656
+ assoc_connector_type = assoc_connector.type
657
+ if assoc_connector_type&.multiplicity
658
+ cardinality = assoc_connector_type.multiplicity.split("..")
659
+ cardinality.unshift("1") if cardinality.length == 1
660
+ min, max = cardinality
661
+ end
662
+ assoc_connector_role = assoc_connector.role
663
+ # Does role has name attribute? Or get name from model?
664
+ # attribute_name = assoc_connector_role.name if assoc_connector_role
665
+ attribute_name = assoc_connector.model.name if assoc_connector_role
666
+ cardinality = cardinality_min_max_value(min, max)
667
+ end
668
+
669
+ [cardinality, attribute_name]
670
+ end
671
+
672
+ # @param owner_xmi_id [String]
673
+ # @param link [Lutaml::Model::Serializable]
674
+ # @return [Array<String, String, Hash, String, String>]
675
+ # @note match return value of serialize_member_type
676
+ def generalization_association(owner_xmi_id, link) # rubocop:disable Metrics/MethodLength
677
+ member_end_type = "generalization"
678
+ xmi_id = link.start
679
+ source_or_target = :source
680
+
681
+ if link.start == owner_xmi_id
682
+ member_end_type = "inheritance"
683
+ xmi_id = link.end
684
+ source_or_target = :target
685
+ end
686
+
687
+ member_end = member_end_name(xmi_id, source_or_target, link.name)
688
+
689
+ member_end_cardinality, _member_end_attribute_name =
690
+ fetch_owned_attribute_node(xmi_id)
691
+
692
+ [member_end, member_end_type, member_end_cardinality, nil, xmi_id]
693
+ end
694
+
695
+ # Multiple items if search type is idref. Should search association?
696
+ # @param xmi_id [String]
697
+ # @return [Array<Hash, String>]
698
+ # @note xpath
699
+ # %(//ownedAttribute[@association]/type[@xmi:idref="#{xmi_id}"])
700
+ def fetch_owned_attribute_node(xmi_id) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
701
+ all_elements = all_packaged_elements
702
+
703
+ owned_attributes = all_elements.map(&:owned_attribute).flatten
704
+ oa = owned_attributes.find do |a|
705
+ !!a.association && a.uml_type && a.uml_type.idref == xmi_id
706
+ end
707
+
708
+ if oa
709
+ cardinality = cardinality_min_max_value(
710
+ oa.lower_value&.value, oa.upper_value&.value
711
+ )
712
+ oa_name = oa.name
713
+ end
714
+
715
+ [cardinality, oa_name]
716
+ end
717
+
718
+ # @param klass_id [String]
719
+ # @return [Lutaml::Model::Serializable]
720
+ # @note xpath %(//element[@xmi:idref="#{klass['xmi:id']}"])
721
+ def fetch_element(klass_id)
722
+ @xmi_root_model.extension.elements.element.find do |e|
723
+ e.idref == klass_id
724
+ end
725
+ end
726
+
727
+ # @param klass [Lutaml::Model::Serializable]
728
+ # @param with_assoc [Boolean]
729
+ # @return [Array<Hash>]
730
+ # @note xpath .//ownedAttribute[@xmi:type="uml:Property"]
731
+ def serialize_class_attributes(klass, with_assoc: false) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
732
+ klass.owned_attribute.select { |attr| attr.type?("uml:Property") }
733
+ .map do |oa|
734
+ if with_assoc || oa.association.nil?
735
+ attrs = build_class_attributes(oa)
736
+
737
+ if with_assoc && oa.association
738
+ attrs[:association] = oa.association
739
+ attrs[:definition] = loopup_assoc_def(oa.association)
740
+ attrs[:type_ns] = get_ns_by_xmi_id(attrs[:xmi_id])
741
+ end
742
+
743
+ attrs
744
+ end
745
+ end.compact
746
+ end
747
+
748
+ def loopup_assoc_def(association)
749
+ connector = fetch_connector(association)
750
+ connector&.documentation&.value
751
+ end
752
+
753
+ # @return [Array<Hash>]
754
+ def serialize_gml_attributes
755
+ element = find_packaged_element_by_name("_Feature")
756
+ attrs = serialize_class_attributes(element, with_assoc: true)
757
+ attrs.each { |attr| attr[:upper_klass] = "gml" }
758
+ end
759
+
760
+ # @return [Array<Hash>]
761
+ def serialize_core_attributes
762
+ element = find_packaged_element_by_name("_CityObject")
763
+ attrs = serialize_class_attributes(element, with_assoc: false)
764
+ attrs.each { |attr| attr[:upper_klass] = "core" }
765
+ end
766
+
767
+ # @return [Array<Hash>]
768
+ def select_gen_attributes
769
+ element = find_packaged_element_by_name("gen")
770
+ gen_attr_element = find_packaged_element_by_name("_genericAttribute")
771
+
772
+ element.packaged_element.select do |e|
773
+ e.type?("uml:Class") &&
774
+ e.generalization&.first&.general == gen_attr_element.id
775
+ end
776
+ end
777
+
778
+ # @return [Array<Hash>]
779
+ def serialize_gen_attributes
780
+ klasses = select_gen_attributes
781
+
782
+ klasses.map do |klass|
783
+ attr = serialize_class_attributes(klass, with_assoc: false)
784
+ attr.first[:name] = klass.name
785
+ attr.first[:type] = "gen:#{klass.name}"
786
+ attr.first[:upper_klass] = "gen"
787
+ attr
788
+ end.flatten!
789
+ end
790
+
791
+ # @param type [String]
792
+ # @return [String]
793
+ def get_ns_by_type(type)
794
+ return unless type
795
+
796
+ p = find_klass_packaged_element_by_name(type)
797
+ return unless p
798
+
799
+ find_upper_level_packaged_element(p.id)
800
+ end
801
+
802
+ # @param xmi_id [String]
803
+ # @return [String]
804
+ def get_ns_by_xmi_id(xmi_id)
805
+ return unless xmi_id
806
+
807
+ p = find_packaged_element_by_id(xmi_id)
808
+ return unless p
809
+
810
+ find_upper_level_packaged_element(p.id)
811
+ end
812
+
813
+ # @param klass_id [String]
814
+ # @return [Array<Hash>]
815
+ def build_class_attributes(owned_attr) # rubocop:disable Metrics/MethodLength
816
+ uml_type = owned_attr.uml_type
817
+ uml_type_idref = uml_type.idref if uml_type
818
+
819
+ {
820
+ id: owned_attr.id,
821
+ name: owned_attr.name,
822
+ type: lookup_entity_name(uml_type_idref) || uml_type_idref,
823
+ xmi_id: uml_type_idref,
824
+ is_derived: owned_attr.is_derived,
825
+ cardinality: cardinality_min_max_value(
826
+ owned_attr.lower_value&.value,
827
+ owned_attr.upper_value&.value,
828
+ ),
829
+ definition: lookup_attribute_documentation(owned_attr.id),
830
+ }
831
+ end
832
+
833
+ # @param min [String]
834
+ # @param max [String]
835
+ # @return [Hash]
836
+ def cardinality_min_max_value(min, max)
837
+ {
838
+ min: min,
839
+ max: max,
840
+ }
841
+ end
842
+
843
+ # @node [Lutaml::Model::Serializable]
844
+ # @attr_name [String]
845
+ # @return [String]
846
+ # @note xpath %(//element[@xmi:idref="#{xmi_id}"]/properties)
847
+ def doc_node_attribute_value(node_id, attr_name)
848
+ doc_node = fetch_element(node_id)
849
+ return unless doc_node
850
+
851
+ doc_node.properties&.send(
852
+ Lutaml::Model::Utils.snake_case(attr_name).to_sym,
853
+ )
854
+ end
855
+
856
+ # @param xmi_id [String]
857
+ # @return [Lutaml::Model::Serializable]
858
+ # @note xpath %(//attribute[@xmi:idref="#{xmi_id}"])
859
+ def fetch_attribute_node(xmi_id)
860
+ attribute_node = nil
861
+ @xmi_root_model.extension.elements.element.each do |e|
862
+ if e.attributes&.attribute
863
+ e.attributes.attribute.each do |a|
864
+ attribute_node = a if a.idref == xmi_id
865
+ end
866
+ end
867
+ end
868
+ attribute_node
869
+ end
870
+
871
+ # @param xmi_id [String]
872
+ # @return [String]
873
+ # @note xpath %(//attribute[@xmi:idref="#{xmi_id}"]/documentation)
874
+ def lookup_attribute_documentation(xmi_id)
875
+ attribute_node = fetch_attribute_node(xmi_id)
876
+
877
+ return unless attribute_node&.documentation
878
+
879
+ attribute_node&.documentation&.value
880
+ end
881
+
882
+ # @param xmi_id [String]
883
+ # @return [String]
884
+ def lookup_element_prop_documentation(xmi_id)
885
+ element_node = @xmi_root_model.extension.elements.element.find do |e|
886
+ e.idref == xmi_id
887
+ end
888
+
889
+ return unless element_node&.properties
890
+
891
+ element_node&.properties&.documentation
892
+ end
893
+
894
+ # @param xmi_id [String]
895
+ # @return [String]
896
+ def lookup_entity_name(xmi_id)
897
+ model_node_name_by_xmi_id(xmi_id) if @xmi_cache.empty?
898
+ @xmi_cache[xmi_id]
899
+ end
900
+
901
+ # @param xmi_id [String]
902
+ # @param source_or_target [String]
903
+ # @return [String]
904
+ def connector_node_by_id(xmi_id, source_or_target)
905
+ @xmi_root_model.extension.connectors.connector.find do |con|
906
+ con.send(source_or_target.to_sym).idref == xmi_id
907
+ end
908
+ end
909
+
910
+ # @param xmi_id [String]
911
+ # @param source_or_target [String]
912
+ # @return [String]
913
+ def connector_name_by_source_or_target(xmi_id, source_or_target)
914
+ node = connector_node_by_id(xmi_id, source_or_target)
915
+ return if node.nil? ||
916
+ node.send(source_or_target.to_sym).nil? ||
917
+ node.send(source_or_target.to_sym).model.nil?
918
+
919
+ node.send(source_or_target.to_sym).model.name
920
+ end
921
+
922
+ # @param xmi_id [String]
923
+ # @param source_or_target [String]
924
+ # @return [String]
925
+ def connector_labels(xmi_id, source_or_target)
926
+ node = connector_node_by_id(xmi_id, source_or_target)
927
+ return if node.nil?
928
+
929
+ node.labels&.rt || node.labels&.lt
930
+ end
931
+
932
+ # @param xmi_id [String]
933
+ # @return [String]
934
+ # @note xpath %(//source[@xmi:idref="#{xmi_id}"]/model)
935
+ def connector_source_name(xmi_id)
936
+ connector_name_by_source_or_target(xmi_id, :source)
937
+ end
938
+
939
+ # @param xmi_id [String]
940
+ # @return [String]
941
+ # @note xpath %(//target[@xmi:idref="#{xmi_id}"]/model)
942
+ def connector_target_name(xmi_id)
943
+ connector_name_by_source_or_target(xmi_id, :target)
944
+ end
945
+
946
+ # @param xmi_id [String]
947
+ # @return [String]
948
+ # @note xpath %(//*[@xmi:id="#{xmi_id}"])
949
+ def model_node_name_by_xmi_id(xmi_id)
950
+ id_name_mapping = Hash.new
951
+ map_id_name(id_name_mapping, @xmi_root_model)
952
+ @xmi_cache = id_name_mapping
953
+ @xmi_cache[xmi_id]
954
+ end
955
+
956
+ # @return [Array<Xmi::Uml::PackagedElement>]
957
+ def all_packaged_elements
958
+ all_elements = []
959
+ packaged_element_roots = @xmi_root_model.model.packaged_element +
960
+ @xmi_root_model.extension.primitive_types.packaged_element +
961
+ @xmi_root_model.extension.profiles.profile.map(&:packaged_element)
962
+
963
+ packaged_element_roots.flatten.each do |e|
964
+ select_all_packaged_elements(all_elements, e, nil)
965
+ end
966
+
967
+ all_elements
968
+ end
969
+
970
+ # @param items [Array<Lutaml::Model::Serializable>]
971
+ # @param model [Lutaml::Model::Serializable]
972
+ # @param type [String] nil for any
973
+ def select_all_items(items, model, type, method)
974
+ iterate_tree(items, model, type, method.to_sym)
975
+ end
976
+
977
+ # @param all_elements [Array<Lutaml::Model::Serializable>]
978
+ # @param model [Lutaml::Model::Serializable]
979
+ # @param type [String] nil for any
980
+ # @note xpath ./packagedElement[@xmi:type="#{type}"]
981
+ def select_all_packaged_elements(all_elements, model, type)
982
+ select_all_items(all_elements, model, type, :packaged_element)
983
+ all_elements.delete_if do |e|
984
+ !e.is_a?(Xmi::Uml::PackagedElement)
985
+ end
986
+ end
987
+
988
+ # @param result [Array<Lutaml::Model::Serializable>]
989
+ # @param node [Lutaml::Model::Serializable]
990
+ # @param type [String] nil for any
991
+ # @param children_method [String] method to determine children exist
992
+ def iterate_tree(result, node, type, children_method) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
993
+ result << node if type.nil? || node.type == type
994
+ return unless node.send(children_method.to_sym)
995
+
996
+ node.send(children_method.to_sym).each do |sub_node|
997
+ if sub_node.send(children_method.to_sym)
998
+ iterate_tree(result, sub_node, type, children_method)
999
+ elsif type.nil? || sub_node.type == type
1000
+ result << sub_node
1001
+ end
1002
+ end
1003
+ end
1004
+
1005
+ # @param result [Hash]
1006
+ # @param node [Lutaml::Model::Serializable]
1007
+ # @note set id as key and name as value into result
1008
+ # if id and name are found
1009
+ def map_id_name(result, node) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
1010
+ return if node.nil?
1011
+
1012
+ if node.is_a?(Array)
1013
+ node.each do |arr_item|
1014
+ map_id_name(result, arr_item)
1015
+ end
1016
+ elsif node.class.methods.include?(:attributes)
1017
+ attrs = node.class.attributes
1018
+
1019
+ if attrs.has_key?(:id) && attrs.has_key?(:name)
1020
+ result[node.id] = node.name
1021
+ end
1022
+
1023
+ attrs.each_pair do |k, _v|
1024
+ map_id_name(result, node.send(k))
1025
+ end
1026
+ end
1027
+ end
1028
+ end
1029
+ end
1030
+ end
1031
+ end