ea 0.1.0 → 0.1.4

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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +125 -0
  3. data/Rakefile +12 -4
  4. data/TODO.next/00-publish-blocking-bugs.md +74 -0
  5. data/TODO.next/01-standalone-ea-gem-identity.md +76 -0
  6. data/TODO.next/02-optional-lutaml-uml-dependency.md +47 -0
  7. data/TODO.next/03-slim-lutaml-uml.md +79 -0
  8. data/TODO.next/04-loader-registry-for-uml-repository.md +49 -0
  9. data/TODO.next/05-extract-shared-transformer-methods.md +14 -0
  10. data/TODO.next/06-deduplicate-stereotype-loading.md +17 -0
  11. data/TODO.next/07-transformer-registry-in-factory.md +20 -0
  12. data/TODO.next/08-connector-type-registry.md +27 -0
  13. data/TODO.next/09-element-renderer-registry.md +29 -0
  14. data/TODO.next/10-connector-renderer-lsp.md +18 -0
  15. data/TODO.next/11-consolidate-style-knowledge.md +33 -0
  16. data/TODO.next/12-data-driven-from-db-row.md +24 -0
  17. data/TODO.next/13-extract-duplicated-methods.md +17 -0
  18. data/TODO.next/14-remove-dead-code.md +10 -0
  19. data/TODO.next/15-narrow-exception-handling.md +39 -0
  20. data/TODO.next/16-repository-indexes.md +28 -0
  21. data/TODO.next/17-fix-spec-quality-and-coverage.md +32 -0
  22. data/TODO.next/18-xmi-tool-specific-parser-architecture.md +172 -0
  23. data/TODO.next/19-fix-ea-gemspec-dependency-declarations.md +56 -0
  24. data/TODO.next/20-ci-requires-unreleased-lutaml-uml.md +63 -0
  25. data/TODO.next/21-qeatoxmi-via-xmi-gem.md +340 -0
  26. data/TODO.next/22-strip-respond-to-from-qeatoxmi-specs.md +32 -0
  27. data/TODO.next/23-cleanup-idallocator.md +41 -0
  28. data/TODO.next/24-tighten-parity-specs.md +42 -0
  29. data/TODO.next/25-sparx-eaid-format-for-synthesized-ids.md +62 -0
  30. data/TODO.next/26-fix-uppervalue-lowervalue-count-gap.md +51 -0
  31. data/TODO.next/27-extract-cardinality-module.md +68 -0
  32. data/TODO.next/28-extract-xml-sanitizer.md +51 -0
  33. data/TODO.next/29-ocp-registry-for-classifier-builders.md +58 -0
  34. data/TODO.next/30-struct-return-for-association-end.md +37 -0
  35. data/TODO.next/31-idallocator-specs.md +27 -0
  36. data/TODO.next/32-phase2-gap-sentinel-specs.md +53 -0
  37. data/TODO.next/33-normalize-lower-cleanup.md +30 -0
  38. data/TODO.next/34-document-member-end-order-rt-prefix.md +29 -0
  39. data/TODO.next/35-walk-runstate-for-instance-slots.md +76 -0
  40. data/TODO.next/36-wire-interface-realization.md +50 -0
  41. data/TODO.next/37-visibility-returns-real-booleans.md +36 -0
  42. data/config/diagram_styles.yml +200 -0
  43. data/config/model_transformations.yml +266 -0
  44. data/config/qea_schema.yml +1024 -0
  45. data/docs/ea_to_uml_type_mapping.md +89 -0
  46. data/docs/xmi_qea_conversion_capabilities.md +99 -0
  47. data/examples/lur/20251010_current_plateau_v5.1.lur +0 -0
  48. data/examples/lur/basic.lur +0 -0
  49. data/examples/lur/test-output.lur +0 -0
  50. data/examples/lur/test.lur +0 -0
  51. data/examples/lur_basic_usage.rb +221 -0
  52. data/examples/lur_cli_workflow.rb +263 -0
  53. data/examples/lur_statistics.rb +326 -0
  54. data/examples/qea/20251010_current_plateau_v5.1.qea +0 -0
  55. data/examples/qea/ArcGISWorkspace_template.qea +0 -0
  56. data/examples/qea/README_qea_parser.adoc +230 -0
  57. data/examples/qea/UmlModel_template.qea +0 -0
  58. data/examples/qea/basic.qea +0 -0
  59. data/examples/qea/simple.qea +0 -0
  60. data/examples/qea/simple_example.qea +0 -0
  61. data/examples/qea/test.qea +0 -0
  62. data/examples/qea_standalone_query.rb +73 -0
  63. data/examples/qea_to_repository.rb +51 -0
  64. data/examples/smoke_test_real_qea.rb +81 -0
  65. data/exe/ea +7 -0
  66. data/lib/ea/cli/app.rb +72 -0
  67. data/lib/ea/cli/command/base.rb +80 -0
  68. data/lib/ea/cli/command/convert.rb +62 -0
  69. data/lib/ea/cli/command/diagrams.rb +81 -0
  70. data/lib/ea/cli/command/list.rb +61 -0
  71. data/lib/ea/cli/command/parse.rb +29 -0
  72. data/lib/ea/cli/command/stats.rb +20 -0
  73. data/lib/ea/cli/command/validate.rb +41 -0
  74. data/lib/ea/cli/command.rb +15 -0
  75. data/lib/ea/cli/error.rb +34 -0
  76. data/lib/ea/cli/output/formatter.rb +34 -0
  77. data/lib/ea/cli/output/json_formatter.rb +20 -0
  78. data/lib/ea/cli/output/table_formatter.rb +42 -0
  79. data/lib/ea/cli/output/yaml_formatter.rb +20 -0
  80. data/lib/ea/cli/output.rb +56 -0
  81. data/lib/ea/cli.rb +17 -0
  82. data/lib/ea/diagram/configuration.rb +379 -0
  83. data/lib/ea/diagram/element_renderers/base_renderer.rb +77 -0
  84. data/lib/ea/diagram/element_renderers/class_renderer.rb +323 -0
  85. data/lib/ea/diagram/element_renderers/connector_renderer.rb +41 -0
  86. data/lib/ea/diagram/element_renderers/package_renderer.rb +61 -0
  87. data/lib/ea/diagram/element_renderers.rb +43 -0
  88. data/lib/ea/diagram/extractor.rb +560 -0
  89. data/lib/ea/diagram/layout_engine.rb +170 -0
  90. data/lib/ea/diagram/path_builder.rb +202 -0
  91. data/lib/ea/diagram/style_parser.rb +42 -0
  92. data/lib/ea/diagram/style_resolver.rb +276 -0
  93. data/lib/ea/diagram/svg_renderer.rb +274 -0
  94. data/lib/ea/diagram/util.rb +73 -0
  95. data/lib/ea/diagram.rb +47 -0
  96. data/lib/ea/qea/benchmark.rb +210 -0
  97. data/lib/ea/qea/database.rb +308 -0
  98. data/lib/ea/qea/factory/association_builder.rb +203 -0
  99. data/lib/ea/qea/factory/association_transformer.rb +91 -0
  100. data/lib/ea/qea/factory/attribute_tag_transformer.rb +57 -0
  101. data/lib/ea/qea/factory/attribute_transformer.rb +93 -0
  102. data/lib/ea/qea/factory/base_transformer.rb +177 -0
  103. data/lib/ea/qea/factory/class_transformer.rb +116 -0
  104. data/lib/ea/qea/factory/constraint_transformer.rb +75 -0
  105. data/lib/ea/qea/factory/data_type_transformer.rb +77 -0
  106. data/lib/ea/qea/factory/diagram_transformer.rb +157 -0
  107. data/lib/ea/qea/factory/document_builder.rb +283 -0
  108. data/lib/ea/qea/factory/ea_to_uml_factory.rb +229 -0
  109. data/lib/ea/qea/factory/enum_transformer.rb +74 -0
  110. data/lib/ea/qea/factory/generalization_builder.rb +227 -0
  111. data/lib/ea/qea/factory/generalization_transformer.rb +98 -0
  112. data/lib/ea/qea/factory/instance_transformer.rb +68 -0
  113. data/lib/ea/qea/factory/object_property_transformer.rb +58 -0
  114. data/lib/ea/qea/factory/operation_transformer.rb +66 -0
  115. data/lib/ea/qea/factory/package_transformer.rb +145 -0
  116. data/lib/ea/qea/factory/reference_resolver.rb +99 -0
  117. data/lib/ea/qea/factory/stereotype_loader.rb +39 -0
  118. data/lib/ea/qea/factory/tagged_value_transformer.rb +38 -0
  119. data/lib/ea/qea/factory/transformer_registry.rb +80 -0
  120. data/lib/ea/qea/factory.rb +37 -0
  121. data/lib/ea/qea/file_detector.rb +178 -0
  122. data/lib/ea/qea/infrastructure/database_connection.rb +100 -0
  123. data/lib/ea/qea/infrastructure/schema_reader.rb +136 -0
  124. data/lib/ea/qea/infrastructure/table_reader.rb +224 -0
  125. data/lib/ea/qea/infrastructure.rb +12 -0
  126. data/lib/ea/qea/models/base_model.rb +59 -0
  127. data/lib/ea/qea/models/ea_attribute.rb +109 -0
  128. data/lib/ea/qea/models/ea_attribute_tag.rb +100 -0
  129. data/lib/ea/qea/models/ea_complexity_type.rb +79 -0
  130. data/lib/ea/qea/models/ea_connector.rb +160 -0
  131. data/lib/ea/qea/models/ea_connector_type.rb +60 -0
  132. data/lib/ea/qea/models/ea_constraint_type.rb +63 -0
  133. data/lib/ea/qea/models/ea_datatype.rb +104 -0
  134. data/lib/ea/qea/models/ea_diagram.rb +115 -0
  135. data/lib/ea/qea/models/ea_diagram_link.rb +78 -0
  136. data/lib/ea/qea/models/ea_diagram_object.rb +73 -0
  137. data/lib/ea/qea/models/ea_diagram_type.rb +56 -0
  138. data/lib/ea/qea/models/ea_document.rb +63 -0
  139. data/lib/ea/qea/models/ea_object.rb +223 -0
  140. data/lib/ea/qea/models/ea_object_constraint.rb +53 -0
  141. data/lib/ea/qea/models/ea_object_property.rb +87 -0
  142. data/lib/ea/qea/models/ea_object_type.rb +73 -0
  143. data/lib/ea/qea/models/ea_operation.rb +127 -0
  144. data/lib/ea/qea/models/ea_operation_param.rb +76 -0
  145. data/lib/ea/qea/models/ea_package.rb +78 -0
  146. data/lib/ea/qea/models/ea_script.rb +62 -0
  147. data/lib/ea/qea/models/ea_status_type.rb +66 -0
  148. data/lib/ea/qea/models/ea_stereotype.rb +57 -0
  149. data/lib/ea/qea/models/ea_tagged_value.rb +99 -0
  150. data/lib/ea/qea/models/ea_xref.rb +165 -0
  151. data/lib/ea/qea/models.rb +35 -0
  152. data/lib/ea/qea/repositories/base_repository.rb +225 -0
  153. data/lib/ea/qea/repositories/object_repository.rb +219 -0
  154. data/lib/ea/qea/repositories.rb +10 -0
  155. data/lib/ea/qea/services/configuration.rb +211 -0
  156. data/lib/ea/qea/services/database_loader.rb +191 -0
  157. data/lib/ea/qea/services.rb +10 -0
  158. data/lib/ea/qea/validation/association_validator.rb +73 -0
  159. data/lib/ea/qea/validation/attribute_validator.rb +91 -0
  160. data/lib/ea/qea/validation/base_validator.rb +331 -0
  161. data/lib/ea/qea/validation/class_validator.rb +121 -0
  162. data/lib/ea/qea/validation/database/circular_reference_validator.rb +109 -0
  163. data/lib/ea/qea/validation/database/orphan_validator.rb +153 -0
  164. data/lib/ea/qea/validation/database/referential_integrity_validator.rb +128 -0
  165. data/lib/ea/qea/validation/database.rb +16 -0
  166. data/lib/ea/qea/validation/diagram_validator.rb +112 -0
  167. data/lib/ea/qea/validation/formatters/json_formatter.rb +137 -0
  168. data/lib/ea/qea/validation/formatters/text_formatter.rb +235 -0
  169. data/lib/ea/qea/validation/formatters.rb +12 -0
  170. data/lib/ea/qea/validation/operation_validator.rb +71 -0
  171. data/lib/ea/qea/validation/package_validator.rb +111 -0
  172. data/lib/ea/qea/validation/validation_engine.rb +387 -0
  173. data/lib/ea/qea/validation/validation_message.rb +144 -0
  174. data/lib/ea/qea/validation/validation_result.rb +210 -0
  175. data/lib/ea/qea/validation/validator_registry.rb +134 -0
  176. data/lib/ea/qea/validation.rb +28 -0
  177. data/lib/ea/qea/verification/comparison_result.rb +264 -0
  178. data/lib/ea/qea/verification/document_normalizer.rb +169 -0
  179. data/lib/ea/qea/verification/document_verifier.rb +322 -0
  180. data/lib/ea/qea/verification/element_comparator.rb +277 -0
  181. data/lib/ea/qea/verification/structure_matcher.rb +287 -0
  182. data/lib/ea/qea/verification.rb +14 -0
  183. data/lib/ea/qea.rb +185 -0
  184. data/lib/ea/transformations/configuration.rb +333 -0
  185. data/lib/ea/transformations/format_registry.rb +366 -0
  186. data/lib/ea/transformations/parsers/base_parser.rb +482 -0
  187. data/lib/ea/transformations/parsers/qea_parser.rb +401 -0
  188. data/lib/ea/transformations/parsers/xmi_parser.rb +243 -0
  189. data/lib/ea/transformations/transformation_engine.rb +390 -0
  190. data/lib/ea/transformations.rb +85 -0
  191. data/lib/ea/transformers/qea_to_xmi/association_end.rb +19 -0
  192. data/lib/ea/transformers/qea_to_xmi/cardinality.rb +96 -0
  193. data/lib/ea/transformers/qea_to_xmi/context.rb +106 -0
  194. data/lib/ea/transformers/qea_to_xmi/guid_format.rb +56 -0
  195. data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +92 -0
  196. data/lib/ea/transformers/qea_to_xmi/run_state.rb +107 -0
  197. data/lib/ea/transformers/qea_to_xmi/transformer.rb +607 -0
  198. data/lib/ea/transformers/qea_to_xmi/visibility.rb +73 -0
  199. data/lib/ea/transformers/qea_to_xmi.rb +29 -0
  200. data/lib/ea/transformers/uml_to_xmi/id_generator.rb +54 -0
  201. data/lib/ea/transformers/uml_to_xmi/transformer.rb +152 -0
  202. data/lib/ea/transformers/uml_to_xmi/writer.rb +96 -0
  203. data/lib/ea/transformers/uml_to_xmi.rb +16 -0
  204. data/lib/ea/transformers.rb +34 -0
  205. data/lib/ea/version.rb +1 -1
  206. data/lib/ea/xmi/liquid_drops/association_drop.rb +56 -0
  207. data/lib/ea/xmi/liquid_drops/attribute_drop.rb +72 -0
  208. data/lib/ea/xmi/liquid_drops/cardinality_drop.rb +35 -0
  209. data/lib/ea/xmi/liquid_drops/connector_drop.rb +54 -0
  210. data/lib/ea/xmi/liquid_drops/constraint_drop.rb +29 -0
  211. data/lib/ea/xmi/liquid_drops/data_type_drop.rb +63 -0
  212. data/lib/ea/xmi/liquid_drops/dependency_drop.rb +36 -0
  213. data/lib/ea/xmi/liquid_drops/diagram_drop.rb +34 -0
  214. data/lib/ea/xmi/liquid_drops/enum_drop.rb +49 -0
  215. data/lib/ea/xmi/liquid_drops/enum_owned_literal_drop.rb +25 -0
  216. data/lib/ea/xmi/liquid_drops/generalization_attribute_drop.rb +87 -0
  217. data/lib/ea/xmi/liquid_drops/generalization_drop.rb +127 -0
  218. data/lib/ea/xmi/liquid_drops/klass_drop.rb +191 -0
  219. data/lib/ea/xmi/liquid_drops/operation_drop.rb +29 -0
  220. data/lib/ea/xmi/liquid_drops/package_drop.rb +108 -0
  221. data/lib/ea/xmi/liquid_drops/root_drop.rb +34 -0
  222. data/lib/ea/xmi/liquid_drops/source_target_drop.rb +43 -0
  223. data/lib/ea/xmi/lookup_service.rb +89 -0
  224. data/lib/ea/xmi/parser.rb +919 -0
  225. data/lib/ea/xmi.rb +35 -0
  226. data/lib/ea.rb +10 -1
  227. metadata +382 -9
@@ -0,0 +1,919 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "lutaml/path"
5
+
6
+ module Ea
7
+ module Xmi
8
+ # Parses EA XMI files into Lutaml::Uml::Document objects.
9
+ #
10
+ # Consolidates what was previously 6 separate modules
11
+ # (Xml, XmiBase, XmiConnector, XmiClassMembers, XmiToUml,
12
+ # XmiToUmlGeneralization) into a single coherent class.
13
+ #
14
+ # The XMI format handled here is EA/Sparx-specific.
15
+ class Parser
16
+ class << self
17
+ # Parse an XMI file into a UML Document.
18
+ def parse(xml)
19
+ new.parse(get_xmi_model(xml))
20
+ end
21
+
22
+ # Parse XMI and serialize to Liquid drops for template rendering.
23
+ def serialize_to_liquid(xml, guidance = nil)
24
+ new.serialize_to_liquid(get_xmi_model(xml), guidance)
25
+ end
26
+
27
+ private
28
+
29
+ def get_xmi_model(xml)
30
+ ::Xmi::Sparx::Root.parse_xml(File.read(xml))
31
+ end
32
+ end
33
+
34
+ # Public instance methods
35
+
36
+ def parse(xmi_model)
37
+ setup_model(xmi_model)
38
+ build_document(xmi_model)
39
+ end
40
+
41
+ def serialize_to_liquid(xmi_model, guidance = nil)
42
+ setup_model(xmi_model)
43
+ document = build_document(xmi_model)
44
+ lookup = LookupService.new(self)
45
+ options = {
46
+ xmi_root_model: @xmi_root_model,
47
+ id_name_mapping: @id_name_mapping,
48
+ lookup: lookup,
49
+ with_gen: true,
50
+ with_absolute_path: true,
51
+ }
52
+ LiquidDrops::RootDrop.new(document, guidance, options)
53
+ end
54
+
55
+ # Public read access for collaborators (LookupService)
56
+ attr_reader :xmi_root_model
57
+
58
+ def xmi_index
59
+ if @xmi_index.nil? && @xmi_root_model
60
+ @xmi_index = @xmi_root_model.index
61
+ @id_name_mapping ||= @xmi_index.id_name_map
62
+ end
63
+ @xmi_index
64
+ end
65
+
66
+ def id_name_mapping
67
+ @id_name_mapping
68
+ end
69
+
70
+ # Public lookup methods used by LookupService and Liquid drops
71
+
72
+ def fetch_connector(link_id)
73
+ xmi_index.find_connector(link_id)
74
+ end
75
+
76
+ def fetch_definition_node_value(link_id, node_name)
77
+ connector_node = fetch_connector(link_id)
78
+ return nil unless connector_node
79
+
80
+ node = connector_node.public_send(node_name.to_sym)
81
+ return nil unless node
82
+
83
+ documentation = node.documentation
84
+ if documentation.is_a?(::Xmi::Sparx::Element::Documentation)
85
+ documentation&.value
86
+ else
87
+ documentation
88
+ end
89
+ end
90
+
91
+ def get_package_name(package) # rubocop:disable Metrics/AbcSize
92
+ return package.name unless package.name.nil?
93
+
94
+ connector = fetch_connector(package.id)
95
+ if connector.target&.model&.name
96
+ return "#{connector.target.model.name} (#{package.type.split(':').last})"
97
+ end
98
+
99
+ "unnamed"
100
+ end
101
+
102
+ def find_packaged_element_by_id(id)
103
+ xmi_index.find_packaged_element(id)
104
+ end
105
+
106
+ def find_upper_level_packaged_element(klass_id)
107
+ xmi_index.find_parent(klass_id)
108
+ end
109
+
110
+ def find_subtype_of_from_owned_attribute_type(id) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
111
+ @pkg_elements_owned_attributes ||= begin
112
+ cache = {}
113
+ all_packaged_elements.each do |e|
114
+ next unless e.owned_attribute
115
+
116
+ e.owned_attribute.each do |oa|
117
+ next unless oa.association && oa.uml_type && oa.uml_type.idref
118
+ cache[oa.uml_type.idref] = e.name
119
+ end
120
+ end
121
+ cache
122
+ end
123
+
124
+ @pkg_elements_owned_attributes[id]
125
+ end
126
+
127
+ def find_subtype_of_from_generalization(id) # rubocop:disable Metrics/AbcSize,Metrics:CyclomaticComplexity,Metrics:MethodLength,Metrics:PerceivedComplexity
128
+ matched_element = xmi_index.find_element(id)
129
+ return unless matched_element&.links&.any?
130
+
131
+ matched_generalization = nil
132
+ matched_element.links.each do |link|
133
+ matched_generalization = link&.generalization&.find { |g| g.start == id }
134
+ break if matched_generalization
135
+ end
136
+
137
+ return if matched_generalization&.end.nil?
138
+ lookup_entity_name(matched_generalization.end)
139
+ end
140
+
141
+ def find_klass_packaged_element(path)
142
+ lutaml_path = Lutaml::Path.parse(path)
143
+ if lutaml_path.segments.one?
144
+ return find_klass_packaged_element_by_name(path)
145
+ end
146
+
147
+ find_klass_packaged_element_by_path(lutaml_path)
148
+ end
149
+
150
+ def find_klass_packaged_element_by_name(name)
151
+ xmi_index.find_packaged_by_name_and_types(name, ["uml:Class", "uml:AssociationClass"])
152
+ end
153
+
154
+ def find_enum_packaged_element_by_name(name)
155
+ xmi_index.packaged_elements_of_type("uml:Enumeration").find { |e| e.name == name }
156
+ end
157
+
158
+ def select_dependencies_by_supplier(supplier_id)
159
+ xmi_index.packaged_elements_of_type("uml:Dependency").select { |e| e.supplier == supplier_id }
160
+ end
161
+
162
+ def select_dependencies_by_client(client_id)
163
+ xmi_index.packaged_elements_of_type("uml:Dependency").select { |e| e.client == client_id }
164
+ end
165
+
166
+ def find_packaged_element_by_name(name)
167
+ xmi_index.packaged_elements.find { |e| e.name == name }
168
+ end
169
+
170
+ def doc_node_attribute_value(node_id, attr_name)
171
+ doc_node = fetch_element(node_id)
172
+ return unless doc_node
173
+
174
+ doc_node.properties&.public_send(
175
+ Lutaml::Model::Utils.snake_case(attr_name).to_sym
176
+ )
177
+ end
178
+
179
+ def lookup_attribute_documentation(xmi_id)
180
+ attribute_node = fetch_attribute_node(xmi_id)
181
+ return unless attribute_node&.documentation
182
+
183
+ attribute_node.documentation.value
184
+ end
185
+
186
+ def lookup_element_prop_documentation(xmi_id)
187
+ element_node = xmi_index.find_element(xmi_id)
188
+ return unless element_node&.properties
189
+
190
+ element_node.properties.documentation
191
+ end
192
+
193
+ def lookup_entity_name(xmi_id)
194
+ @id_name_mapping[xmi_id]
195
+ end
196
+
197
+ def lookup_assoc_def(association)
198
+ connector = fetch_connector(association)
199
+ connector&.documentation&.value
200
+ end
201
+
202
+ def get_ns_by_xmi_id(xmi_id)
203
+ return unless xmi_id
204
+
205
+ p = find_packaged_element_by_id(xmi_id)
206
+ return unless p
207
+
208
+ find_upper_level_packaged_element(p.id)&.name
209
+ end
210
+
211
+ def fetch_element(klass_id)
212
+ xmi_index.find_element(klass_id)
213
+ end
214
+
215
+ def fetch_attribute_node(xmi_id)
216
+ xmi_index.find_attribute(xmi_id)
217
+ end
218
+
219
+ def all_packaged_elements
220
+ xmi_index.packaged_elements
221
+ end
222
+
223
+ def select_all_packaged_elements(all_elements, model, type)
224
+ select_all_items(all_elements, model, type, :packaged_element)
225
+ all_elements.delete_if { |e| !e.is_a?(::Xmi::Uml::PackagedElement) }
226
+ end
227
+
228
+ private
229
+
230
+ # --- Model setup ---
231
+
232
+ def setup_model(xmi_model)
233
+ @xmi_root_model ||= xmi_model
234
+ if @xmi_index.nil?
235
+ @xmi_index = @xmi_root_model.index
236
+ @id_name_mapping = @xmi_index.id_name_map
237
+ end
238
+ end
239
+
240
+ # --- Document building ---
241
+
242
+ def build_document(xmi_model)
243
+ ::Lutaml::Uml::Document.new.tap do |doc|
244
+ doc.name = xmi_model.model.name
245
+ doc.packages = build_packages(xmi_model.model)
246
+ end
247
+ end
248
+
249
+ def build_packages(model)
250
+ return [] if model.packaged_element.nil?
251
+
252
+ packages = model.packaged_element.select { |e| e.type?("uml:Package") }
253
+ packages.map { |package| build_package(package) }
254
+ end
255
+
256
+ def build_package(package)
257
+ ::Lutaml::Uml::Package.new.tap do |pkg|
258
+ pkg.xmi_id = package.id
259
+ pkg.name = get_package_name(package)
260
+ pkg.definition = doc_node_attribute_value(package.id, "documentation")
261
+ st = doc_node_attribute_value(package.id, "stereotype")
262
+ pkg.stereotype = [st] if st
263
+
264
+ pkg.packages = build_packages(package)
265
+ pkg.classes = build_classes(package)
266
+ pkg.enums = build_enums(package)
267
+ pkg.data_types = build_data_types(package)
268
+ pkg.diagrams = build_diagrams(package.id)
269
+ end
270
+ end
271
+
272
+ def build_classes(package)
273
+ return [] if package.packaged_element.nil?
274
+
275
+ klasses = package.packaged_element.select do |e|
276
+ e.type?("uml:Class") || e.type?("uml:AssociationClass") ||
277
+ e.type?("uml:Interface")
278
+ end
279
+
280
+ klasses.map { |klass| build_class(klass) }
281
+ end
282
+
283
+ def build_class(klass) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics:CyclomaticComplexity,Metrics:PerceivedComplexity
284
+ ::Lutaml::Uml::UmlClass.new.tap do |k|
285
+ k.xmi_id = klass.id
286
+ k.name = klass.name
287
+ k.type = klass.type.split(":").last
288
+ k.is_abstract = doc_node_attribute_value(klass.id, "isAbstract")
289
+ k.definition = doc_node_attribute_value(klass.id, "documentation")
290
+ k_st = doc_node_attribute_value(klass.id, "stereotype")
291
+ k.stereotype = [k_st] if k_st
292
+
293
+ k.attributes = build_class_attributes(klass)
294
+ k.associations = build_associations(klass.id)
295
+ k.operations = build_operations(klass)
296
+ k.constraints = build_constraints(klass.id)
297
+ k.association_generalization = build_assoc_generalizations(klass)
298
+
299
+ if klass.type?("uml:Class")
300
+ k.generalization = build_generalization(klass)
301
+ end
302
+ end
303
+ end
304
+
305
+ def build_enums(package) # rubocop:disable Metrics:MethodLength,Metrics:AbcSize
306
+ return [] if package.packaged_element.nil?
307
+
308
+ package.packaged_element
309
+ .select { |e| e.type?("uml:Enumeration") }
310
+ .map do |enum|
311
+ ::Lutaml::Uml::Enum.new.tap do |en|
312
+ en.xmi_id = enum.id
313
+ en.name = enum.name
314
+ en.values = build_values(enum)
315
+ en.definition = doc_node_attribute_value(enum.id, "documentation")
316
+ en_st = doc_node_attribute_value(enum.id, "stereotype")
317
+ en.stereotype = [en_st] if en_st
318
+ end
319
+ end
320
+ end
321
+
322
+ def build_data_types(package) # rubocop:disable Metrics:AbcSize,Metrics:MethodLength
323
+ return [] if package.packaged_element.nil?
324
+
325
+ package.packaged_element
326
+ .select { |e| e.type?("uml:DataType") }
327
+ .map do |dt|
328
+ ::Lutaml::Uml::DataType.new.tap do |data_type|
329
+ data_type.xmi_id = dt.id
330
+ data_type.name = dt.name
331
+ data_type.is_abstract = doc_node_attribute_value(dt.id, "isAbstract")
332
+ data_type.definition = doc_node_attribute_value(dt.id, "documentation")
333
+ dt_st = doc_node_attribute_value(dt.id, "stereotype")
334
+ data_type.stereotype = [dt_st] if dt_st
335
+
336
+ data_type.attributes = build_class_attributes(dt)
337
+ data_type.operations = build_operations(dt)
338
+ data_type.associations = build_associations(dt.id)
339
+ data_type.constraints = build_constraints(dt.id)
340
+ end
341
+ end
342
+ end
343
+
344
+ def build_diagrams(node_id) # rubocop:disable Metrics:AbcSize,Metrics:MethodLength
345
+ return [] if @xmi_root_model.extension&.diagrams&.diagram.nil?
346
+
347
+ diagram_lookup[node_id].map do |diagram|
348
+ ::Lutaml::Uml::Diagram.new.tap do |dia|
349
+ dia.xmi_id = diagram.id
350
+ dia.name = diagram&.properties&.name
351
+ dia.definition = diagram&.properties&.documentation
352
+
353
+ package_id = diagram&.model&.package
354
+ if package_id
355
+ dia.package_id = package_id
356
+ dia.package_name = find_packaged_element_by_id(package_id)&.name
357
+ end
358
+ end
359
+ end
360
+ end
361
+
362
+ def build_class_attributes(klass) # rubocop:disable Metrics:AbcSize,Metrics:CyclomaticComplexity,Metrics:MethodLength
363
+ return [] if klass.owned_attribute.nil?
364
+
365
+ all_props = klass.owned_attribute.select { |attr| attr.type?("uml:Property") }
366
+ all_props.filter_map { |oa| build_attribute(oa) }
367
+ end
368
+
369
+ def build_attribute(owned_attr) # rubocop:disable Metrics:AbcSize,Metrics:MethodLength
370
+ uml_type = owned_attr.uml_type
371
+ uml_type_idref = uml_type.idref if uml_type
372
+
373
+ ::Lutaml::Uml::TopElementAttribute.new.tap do |attr|
374
+ attr.id = owned_attr.id
375
+ attr.name = owned_attr.name
376
+ attr.type = lookup_entity_name(uml_type_idref) || uml_type_idref
377
+ attr.xmi_id = uml_type_idref
378
+ attr.is_derived = !!owned_attr.is_derived
379
+ attr.cardinality = ::Lutaml::Uml::Cardinality.new.tap do |car|
380
+ car.min = owned_attr.lower_value&.value
381
+ car.max = owned_attr.upper_value&.value
382
+ end
383
+ attr.definition = lookup_attribute_documentation(owned_attr.id)
384
+
385
+ if owned_attr.association
386
+ attr.association = owned_attr.association
387
+ attr.definition = lookup_assoc_def(owned_attr.association)
388
+ attr.type_ns = get_ns_by_xmi_id(attr.xmi_id)
389
+ end
390
+ end
391
+ end
392
+
393
+ def build_associations(xmi_id) # rubocop:disable Metrics:AbcSize,Metrics:CyclomaticComplexity,Metrics:MethodLength,Metrics:PerceivedComplexity
394
+ matched_element = xmi_index&.find_element(xmi_id)
395
+ return if !matched_element || !matched_element.links
396
+
397
+ links = []
398
+ matched_element.links.each do |link|
399
+ links << link.association if link.association.any?
400
+ end
401
+
402
+ links.flatten.compact.filter_map do |assoc|
403
+ build_association(assoc, xmi_id)
404
+ end
405
+ end
406
+
407
+ def build_association(assoc, xmi_id) # rubocop:disable Metrics:AbcSize,Metrics:CyclomaticComplexity,Metrics:MethodLength
408
+ link_member = assoc.start == xmi_id ? "end" : "start"
409
+ link_owner = link_member == "start" ? "end" : "start"
410
+
411
+ member_end, member_end_type, member_end_cardinality,
412
+ member_end_attribute_name, member_end_xmi_id =
413
+ serialize_member_type(xmi_id, assoc, link_member)
414
+
415
+ owner_end = serialize_owned_type(xmi_id, assoc, link_owner)
416
+ doc_node = link_member == "start" ? "source" : "target"
417
+ definition = fetch_definition_node_value(assoc.id, doc_node)
418
+
419
+ owner_end_attribute_name = find_owner_attribute_name(xmi_id, assoc.id)
420
+
421
+ return nil unless member_end &&
422
+ ((member_end_type != "aggregation") ||
423
+ (member_end_type == "aggregation" && member_end_attribute_name))
424
+
425
+ ::Lutaml::Uml::Association.new.tap do |association|
426
+ association.xmi_id = assoc.id
427
+ association.member_end = member_end
428
+ association.member_end_type = member_end_type
429
+ association.member_end_cardinality = build_cardinality(member_end_cardinality)
430
+ association.member_end_attribute_name = member_end_attribute_name
431
+ association.member_end_xmi_id = member_end_xmi_id
432
+ association.owner_end = owner_end
433
+ association.owner_end_xmi_id = xmi_id
434
+ association.owner_end_attribute_name = owner_end_attribute_name
435
+ association.definition = definition
436
+ end
437
+ end
438
+
439
+ def build_cardinality(hash)
440
+ return nil unless hash
441
+
442
+ ::Lutaml::Uml::Cardinality.new.tap do |cardinality|
443
+ cardinality.min = hash[:min]
444
+ cardinality.max = hash[:max]
445
+ end
446
+ end
447
+
448
+ def build_operations(klass) # rubocop:disable Metrics:MethodLength,Metrics:AbcSize
449
+ return [] if klass.owned_operation.nil?
450
+
451
+ klass.owned_operation.filter_map do |operation|
452
+ uml_type = operation.uml_type.first
453
+ uml_type_idref = uml_type.idref if uml_type
454
+
455
+ if !operation.class.attributes.key?(:association) || operation.association.nil?
456
+ ::Lutaml::Uml::Operation.new.tap do |op|
457
+ op.id = operation.id
458
+ op.xmi_id = uml_type_idref
459
+ op.name = operation.name
460
+ op.definition = lookup_attribute_documentation(operation.id)
461
+ end
462
+ end
463
+ end
464
+ end
465
+
466
+ def build_constraints(klass_id) # rubocop:disable Metrics:MethodLength,Metrics:AbcSize
467
+ connector_node = fetch_connector(klass_id)
468
+ return [] if connector_node.nil?
469
+
470
+ constraints = %i[source target].map do |st|
471
+ connector_node.public_send(st).constraints.constraint
472
+ end.flatten
473
+
474
+ constraints.map do |constraint|
475
+ ::Lutaml::Uml::Constraint.new.tap do |con|
476
+ con.name = CGI.unescapeHTML(constraint.name)
477
+ con.type = constraint.type
478
+ con.weight = constraint.weight
479
+ con.status = constraint.status
480
+ end
481
+ end
482
+ end
483
+
484
+ def build_values(enum) # rubocop:disable Metrics:MethodLength,Metrics:CyclomaticComplexity,Metrics:AbcSize
485
+ return [] if enum.owned_literal.nil?
486
+
487
+ enum.owned_literal
488
+ .select { |lit| lit.type?("uml:EnumerationLiteral") }
489
+ .map do |owned_literal|
490
+ uml_type_id = owned_literal&.uml_type&.idref
491
+
492
+ ::Lutaml::Uml::Value.new.tap do |value|
493
+ value.name = owned_literal.name
494
+ value.type = lookup_entity_name(uml_type_id) || uml_type_id
495
+ value.definition = lookup_attribute_documentation(owned_literal.id)
496
+ end
497
+ end
498
+ end
499
+
500
+ # --- Generalization building ---
501
+
502
+ def build_generalization(klass) # rubocop:disable Metrics:AbcSize,Metrics:MethodLength,Metrics:CyclomaticComplexity,Metrics:PerceivedComplexity
503
+ uml_general_obj, next_general_node_id = get_uml_general(klass.id)
504
+ return uml_general_obj unless next_general_node_id
505
+
506
+ if uml_general_obj.general
507
+ inherited_props = []
508
+ inherited_assoc_props = []
509
+ level = 0
510
+
511
+ loop_general_item(
512
+ uml_general_obj.general, level, inherited_props, inherited_assoc_props
513
+ )
514
+ uml_general_obj.inherited_props = inherited_props.reverse
515
+ uml_general_obj.inherited_assoc_props = inherited_assoc_props.reverse
516
+ end
517
+
518
+ uml_general_obj
519
+ end
520
+
521
+ def build_generalization_attributes(uml_general_obj) # rubocop:disable Metrics:AbcSize,Metrics:MethodLength
522
+ upper_klass = uml_general_obj.general_upper_klass
523
+ gen_attrs = uml_general_obj.general_attributes
524
+ gen_name = uml_general_obj.general_name
525
+
526
+ gen_attrs&.each do |i|
527
+ name_ns = case i.type_ns
528
+ when "core", "gml"
529
+ upper_klass
530
+ else
531
+ i.type_ns
532
+ end
533
+ name_ns = upper_klass if name_ns.nil?
534
+
535
+ i.name_ns = name_ns
536
+ i.gen_name = gen_name
537
+ i.name = "" if i.name.nil?
538
+ end
539
+
540
+ gen_attrs
541
+ end
542
+
543
+ def get_uml_general(general_id) # rubocop:disable Metrics:AbcSize,Metrics:MethodLength
544
+ general_node = find_packaged_element_by_id(general_id)
545
+ return [] unless general_node
546
+
547
+ general_node_attrs = get_uml_general_attributes(general_node)
548
+ general_upper_klass = find_upper_level_packaged_element(general_id)
549
+ next_general_node_id = general_node.generalization.first&.general
550
+
551
+ uml_general = build_uml_general_node(
552
+ general_id, general_node, general_node_attrs,
553
+ general_upper_klass, next_general_node_id
554
+ )
555
+
556
+ assign_general_properties(uml_general)
557
+
558
+ [uml_general, next_general_node_id]
559
+ end
560
+
561
+ def build_uml_general_node(general_id, general_node, attrs, upper_klass, next_id)
562
+ gen = ::Lutaml::Uml::Generalization.new
563
+ assign_general_basic_props(gen, general_id, general_node, attrs, upper_klass)
564
+ assign_stereotype(gen, general_id)
565
+ assign_parent_generalization(gen, general_node, next_id)
566
+ gen
567
+ end
568
+
569
+ def assign_general_basic_props(gen, general_id, general_node, attrs, upper_klass)
570
+ gen.general_id = general_id
571
+ gen.general_name = general_node.name
572
+ gen.general_attributes = attrs
573
+ gen.general_upper_klass = upper_klass ? get_package_name(upper_klass) : nil
574
+ gen.name = general_node.name
575
+ gen.type = general_node.type
576
+ gen.definition = lookup_element_prop_documentation(general_id)
577
+ end
578
+
579
+ def assign_stereotype(gen, general_id)
580
+ gen_st = doc_node_attribute_value(general_id, "stereotype")
581
+ gen.stereotype = [gen_st] if gen_st
582
+ end
583
+
584
+ def assign_parent_generalization(gen, general_node, next_id)
585
+ return unless next_id
586
+
587
+ gen.general = set_uml_generalization(next_id)
588
+ gen.has_general = true
589
+ gen.general_id = general_node.id
590
+ gen.general_name = general_node.name
591
+ end
592
+
593
+ def assign_general_properties(uml_general)
594
+ uml_general.attributes = build_generalization_attributes(uml_general)
595
+ uml_general.owned_props = uml_general.attributes.select { |a| a.association.nil? }
596
+ uml_general.assoc_props = uml_general.attributes.select(&:association)
597
+ end
598
+
599
+ def get_uml_general_attributes(general_node) # rubocop:disable Metrics:AbcSize,Metrics:MethodLength
600
+ attrs = build_class_attributes(general_node)
601
+
602
+ attrs.map do |attr|
603
+ ::Lutaml::Uml::GeneralAttribute.new.tap do |gen_attr|
604
+ gen_attr.id = attr.id
605
+ gen_attr.name = attr.name
606
+ gen_attr.type = attr.type
607
+ gen_attr.xmi_id = attr.xmi_id
608
+ gen_attr.is_derived = !!attr.is_derived
609
+ gen_attr.cardinality = attr.cardinality
610
+ gen_attr.definition = attr.definition&.strip
611
+ gen_attr.association = attr.association
612
+ gen_attr.has_association = !!attr.association
613
+ gen_attr.type_ns = attr.type_ns
614
+ end
615
+ end
616
+ end
617
+
618
+ def set_uml_generalization(general_id)
619
+ uml_general_obj, next_general_node_id = get_uml_general(general_id)
620
+
621
+ if next_general_node_id
622
+ uml_general_obj.general = set_uml_generalization(next_general_node_id)
623
+ uml_general_obj.has_general = true
624
+ end
625
+
626
+ uml_general_obj
627
+ end
628
+
629
+ def loop_general_item( # rubocop:disable Metrics:MethodLength,Metrics:AbcSize,Metrics:PerceivedComplexity,Metrics:CyclomaticComplexity
630
+ general_item, level, inherited_props, inherited_assoc_props
631
+ )
632
+ gen_upper_klass = general_item.general_upper_klass
633
+ gen_name = general_item.general_name
634
+
635
+ general_item.attributes.reverse_each do |attr|
636
+ attr.upper_klass = gen_upper_klass
637
+ attr.gen_name = gen_name
638
+ attr.level = level
639
+
640
+ if attr.association
641
+ inherited_assoc_props << attr
642
+ else
643
+ inherited_props << attr
644
+ end
645
+ end
646
+
647
+ if general_item&.has_general && general_item.general
648
+ level += 1
649
+ loop_general_item(
650
+ general_item.general, level, inherited_props, inherited_assoc_props
651
+ )
652
+ end
653
+ end
654
+
655
+ def build_assoc_generalizations(klass) # rubocop:disable Metrics:AbcSize
656
+ return [] if klass.generalization.nil? || klass.generalization.empty?
657
+
658
+ klass.generalization.map do |gen|
659
+ ::Lutaml::Uml::AssociationGeneralization.new.tap do |assoc_gen|
660
+ assoc_gen.id = gen.id
661
+ assoc_gen.type = gen.type
662
+ assoc_gen.general = gen.general
663
+ end
664
+ end
665
+ end
666
+
667
+ # --- Connector serialization ---
668
+
669
+ def serialize_owned_type(owner_xmi_id, link, link_owner_name)
670
+ case link
671
+ when ::Xmi::Sparx::Element::NoteLink
672
+ return
673
+ when ::Xmi::Sparx::Element::Generalization
674
+ owner_end, = generalization_association(owner_xmi_id, link)
675
+ return owner_end
676
+ end
677
+
678
+ xmi_id = link.public_send(link_owner_name.to_sym)
679
+ lookup_entity_name(xmi_id) || connector_source_name(xmi_id)
680
+ end
681
+
682
+ def serialize_member_end(owner_xmi_id, link) # rubocop:disable Metrics:MethodLength,Metrics:AbcSize,Metrics:CyclomaticComplexity
683
+ case link.name
684
+ when "NoteLink"
685
+ return
686
+ when "Generalization"
687
+ return generalization_association(owner_xmi_id, link)
688
+ end
689
+
690
+ xmi_id = link.start
691
+ source_or_target = :source
692
+
693
+ if link.start == owner_xmi_id
694
+ xmi_id = link.end
695
+ source_or_target = :target
696
+ end
697
+
698
+ connector = fetch_connector(link.id)
699
+ ea_type = connector&.properties&.ea_type
700
+ member_end_type = ea_type&.downcase
701
+
702
+ member_end = member_end_name(xmi_id, source_or_target, link)
703
+ [member_end, member_end_type, xmi_id]
704
+ end
705
+
706
+ def member_end_name(xmi_id, source_or_target, link) # rubocop:disable Metrics:MethodLength
707
+ connector_label = connector_labels(xmi_id, source_or_target)
708
+ entity_name = lookup_entity_name(xmi_id)
709
+ connector_name = connector_name_by_source_or_target(xmi_id, source_or_target)
710
+
711
+ case link
712
+ when ::Xmi::Sparx::Element::Aggregation
713
+ connector_label || entity_name || connector_name
714
+ else
715
+ entity_name || connector_name
716
+ end
717
+ end
718
+
719
+ def serialize_member_type(owner_xmi_id, link, link_member_name) # rubocop:disable Metrics:MethodLength,Metrics:AbcSize
720
+ member_end, member_end_type, xmi_id =
721
+ serialize_member_end(owner_xmi_id, link)
722
+
723
+ if link.is_a?(::Xmi::Sparx::Element::Association)
724
+ connector_type = link_member_name == "start" ? "source" : "target"
725
+ member_end_cardinality, member_end_attribute_name =
726
+ fetch_assoc_connector(link.id, connector_type)
727
+ else
728
+ member_end_cardinality, member_end_attribute_name =
729
+ fetch_owned_attribute_node(xmi_id)
730
+ end
731
+
732
+ if fetch_connector_name(link.id)
733
+ member_end = fetch_connector_name(link.id)
734
+ end
735
+
736
+ [member_end, member_end_type, member_end_cardinality,
737
+ member_end_attribute_name, xmi_id]
738
+ end
739
+
740
+ def fetch_connector_name(link_id)
741
+ connector = fetch_connector(link_id)
742
+ connector&.name
743
+ end
744
+
745
+ def fetch_assoc_connector(link_id, connector_type)
746
+ connector = fetch_connector(link_id)
747
+ return [nil, nil] unless connector
748
+
749
+ assoc_connector = connector.public_send(connector_type.to_sym)
750
+ return [nil, nil] unless assoc_connector
751
+
752
+ [extract_cardinality(assoc_connector), extract_attribute_name(assoc_connector)]
753
+ end
754
+
755
+ def extract_cardinality(assoc_connector)
756
+ assoc_connector_type = assoc_connector.type
757
+ min = nil
758
+ max = nil
759
+ if assoc_connector_type&.multiplicity
760
+ cardinality = assoc_connector_type.multiplicity.split("..")
761
+ cardinality.unshift("1") if cardinality.length == 1
762
+ min, max = cardinality
763
+ end
764
+ cardinality_min_max_value(min, max)
765
+ end
766
+
767
+ def extract_attribute_name(assoc_connector)
768
+ assoc_connector.role ? assoc_connector.model.name : nil
769
+ end
770
+
771
+ def generalization_association(owner_xmi_id, link) # rubocop:disable Metrics:MethodLength
772
+ member_end_type = "generalization"
773
+ xmi_id = link.start
774
+ source_or_target = :source
775
+
776
+ if link.start == owner_xmi_id
777
+ member_end_type = "inheritance"
778
+ xmi_id = link.end
779
+ source_or_target = :target
780
+ end
781
+
782
+ member_end = member_end_name(xmi_id, source_or_target, link)
783
+ [member_end, member_end_type, xmi_id]
784
+ end
785
+
786
+ def fetch_owned_attribute_node(xmi_id)
787
+ oa = xmi_index.find_owned_attrs_by_type(xmi_id)
788
+ .find { |a| !!a.association }
789
+
790
+ if oa
791
+ cardinality = cardinality_min_max_value(
792
+ oa.lower_value&.value, oa.upper_value&.value
793
+ )
794
+ oa_name = oa.name
795
+ end
796
+
797
+ [cardinality, oa_name]
798
+ end
799
+
800
+ def find_owner_attribute_name(owner_xmi_id, assoc_id)
801
+ owner_node = find_packaged_element_by_id(owner_xmi_id)
802
+ return nil unless owner_node&.owned_attribute
803
+
804
+ owned_attr = owner_node.owned_attribute.find { |oa| oa.association == assoc_id }
805
+ owned_attr&.name
806
+ end
807
+
808
+ # --- Connector lookup index ---
809
+
810
+ def diagram_lookup
811
+ @diagram_lookup ||= begin
812
+ idx = Hash.new { |h, k| h[k] = [] }
813
+ xmi_diagrams.each { |d| idx[d.model.package] << d if d.model&.package }
814
+ idx
815
+ end
816
+ end
817
+
818
+ def xmi_diagrams
819
+ @xmi_root_model.extension&.diagrams&.diagram || []
820
+ end
821
+
822
+ def connector_lookup
823
+ @connector_lookup ||= begin
824
+ lookup = {}
825
+ connectors = @xmi_root_model.extension&.connectors&.connector || []
826
+ connectors.each { |con| index_connector_directions(con, lookup) }
827
+ lookup
828
+ end
829
+ end
830
+
831
+ def index_connector_directions(con, lookup)
832
+ %i[source target].each do |dir|
833
+ idref = con.public_send(dir)&.idref
834
+ lookup[[dir, idref]] = con if idref
835
+ end
836
+ end
837
+
838
+ def connector_node_by_id(xmi_id, source_or_target)
839
+ connector_lookup[[source_or_target.to_sym, xmi_id]]
840
+ end
841
+
842
+ def connector_name_by_source_or_target(xmi_id, source_or_target) # rubocop:disable Metrics:AbcSize
843
+ node = connector_node_by_id(xmi_id, source_or_target)
844
+ return node.name if node&.name
845
+
846
+ return if node.nil? ||
847
+ node.public_send(source_or_target.to_sym).nil? ||
848
+ node.public_send(source_or_target.to_sym).model.nil?
849
+
850
+ node.public_send(source_or_target.to_sym).model.name
851
+ end
852
+
853
+ def connector_labels(xmi_id, source_or_target)
854
+ node = connector_node_by_id(xmi_id, source_or_target)
855
+ return if node.nil?
856
+
857
+ node.labels&.rt || node.labels&.lt
858
+ end
859
+
860
+ def connector_source_name(xmi_id)
861
+ connector_name_by_source_or_target(xmi_id, :source)
862
+ end
863
+
864
+ def cardinality_min_max_value(min, max)
865
+ { min: min, max: max }
866
+ end
867
+
868
+ def select_all_items(items, model, type, method)
869
+ iterate_tree(items, model, type, method.to_sym)
870
+ end
871
+
872
+ def iterate_tree(result, node, type, children_method) # rubocop:disable Metrics:AbcSize,Metrics:CyclomaticComplexity,Metrics:PerceivedComplexity
873
+ result << node if type.nil? || node.type == type
874
+ return unless node.public_send(children_method)
875
+
876
+ node.public_send(children_method).each do |sub_node|
877
+ if sub_node.public_send(children_method)
878
+ iterate_tree(result, sub_node, type, children_method)
879
+ elsif type.nil? || sub_node.type == type
880
+ result << sub_node
881
+ end
882
+ end
883
+ end
884
+
885
+ def find_klass_packaged_element_by_path(path)
886
+ if path.absolute?
887
+ iterate_packaged_element(@xmi_root_model.model, path.segments.map(&:name))
888
+ else
889
+ iterate_relative_packaged_element(path.segments.map(&:name))
890
+ end
891
+ end
892
+
893
+ def iterate_relative_packaged_element(name_array)
894
+ matched_elements = xmi_index.packaged_elements_of_type("uml:Package")
895
+ .select { |e| e.name == name_array[0] }
896
+
897
+ result = matched_elements.map do |e|
898
+ iterate_packaged_element(e, name_array, type: "uml:Class")
899
+ end
900
+
901
+ result.compact.first
902
+ end
903
+
904
+ def iterate_packaged_element(model, name_array, index: 1, type: "uml:Package")
905
+ return model if index == name_array.count
906
+
907
+ model = model.packaged_element.find do |p|
908
+ p.name == name_array[index] && p.type?(type)
909
+ end
910
+
911
+ return nil if model.nil?
912
+
913
+ index += 1
914
+ type = index == name_array.count - 1 ? "uml:Class" : "uml:Package"
915
+ iterate_packaged_element(model, name_array, index: index, type: type)
916
+ end
917
+ end
918
+ end
919
+ end