lutaml 0.9.28 → 0.9.30

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