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,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Transformers
5
+ # Full-fidelity transformer: Ea::Qea::Database → Sparx XMI.
6
+ #
7
+ # Walks the package tree starting at root packages, building
8
+ # Xmi::Sparx::Root / Xmi::Uml::UmlModel / Xmi::Uml::PackagedElement
9
+ # models from each QEA row, then asks the xmi gem to serialize them
10
+ # via `to_xml(use_prefix: true)` to produce Sparx XMI in the canonical
11
+ # mixed-prefix style.
12
+ #
13
+ # Use this for Sparx-to-Sparx round-trip — no intermediate
14
+ # Lutaml::Uml::Document, no loss of Sparx-specific concepts
15
+ # (multiplicities, tagged values, stereotypes, primitive types,
16
+ # instance specifications, association ends). For a tool-agnostic
17
+ # UML document → XMI path, use UmlToXmi.
18
+ module QeaToXmi
19
+ autoload :Transformer, "ea/transformers/qea_to_xmi/transformer"
20
+ autoload :Context, "ea/transformers/qea_to_xmi/context"
21
+ autoload :IdAllocator, "ea/transformers/qea_to_xmi/id_allocator"
22
+ autoload :GuidFormat, "ea/transformers/qea_to_xmi/guid_format"
23
+ autoload :Cardinality, "ea/transformers/qea_to_xmi/cardinality"
24
+ autoload :Visibility, "ea/transformers/qea_to_xmi/visibility"
25
+ autoload :RunState, "ea/transformers/qea_to_xmi/run_state"
26
+ autoload :AssociationEnd, "ea/transformers/qea_to_xmi/association_end"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Transformers
5
+ module UmlToXmi
6
+ # Allocates xmi:id values for UML elements during lossy serialization.
7
+ #
8
+ # Strategy:
9
+ # 1. If the element already has an `xmi_id` (set by the QEA factory
10
+ # during parsing), reuse it.
11
+ # 2. Otherwise synthesize a stable `EAID_…` from a counter.
12
+ #
13
+ # This class is used only by the lossy {UmlToXmi::Transformer} path.
14
+ # The full-fidelity {QeaToXmi} path uses {QeaToXmi::GuidFormat} to
15
+ # normalize raw EA GUIDs.
16
+ class IdGenerator
17
+ PREFIX = "EAID"
18
+ MODEL_ID = "#{PREFIX}_EA_MODEL"
19
+
20
+ def initialize
21
+ @assigned = {} # object_id → xmi:id
22
+ @counter = 0
23
+ end
24
+
25
+ def eaid_for(element)
26
+ key = element.object_id
27
+ @assigned[key] ||= begin
28
+ @counter += 1
29
+ extract_id(element) || synthesize(@counter)
30
+ end
31
+ end
32
+
33
+ def model_id
34
+ MODEL_ID
35
+ end
36
+
37
+ private
38
+
39
+ # `Lutaml::Uml::Value` exposes its XMI identifier via `id` (it does
40
+ # not inherit from TopElement, so it has no `xmi_id`). All other
41
+ # serializable UML types use `xmi_id`.
42
+ def extract_id(element)
43
+ return element.id if element.is_a?(Lutaml::Uml::Value)
44
+
45
+ element.xmi_id
46
+ end
47
+
48
+ def synthesize(n)
49
+ format("%<prefix>s_%<hex>08X", prefix: PREFIX, hex: n)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Transformers
5
+ module UmlToXmi
6
+ # Orchestrates transformation of a {Lutaml::Uml::Document} to Sparx XMI.
7
+ #
8
+ # Walks the UML tree (packages → classifiers → features) and emits XML
9
+ # via {Writer}. Each UML element type has its own private emitter method.
10
+ #
11
+ # This is the LOSSY path — Sparx-specific concepts (stereotypes,
12
+ # multiplicities, tagged values, EA extension block) are not modeled in
13
+ # Lutaml::Uml and therefore not emitted. For Sparx-to-Sparx round-trip
14
+ # with full fidelity, use {Ea::Transformers::QeaToXmi}.
15
+ class Transformer
16
+ MODEL_NAME = "EA_Model"
17
+
18
+ def initialize(document)
19
+ @document = document
20
+ @id_gen = IdGenerator.new
21
+ @writer = Writer.new
22
+ end
23
+
24
+ # @return [String] XMI XML document
25
+ def serialize
26
+ assign_ids!
27
+ @writer.xmi_root do
28
+ @writer.documentation
29
+ @writer.uml_model(@id_gen.model_id, MODEL_NAME) do
30
+ emit_top_level
31
+ end
32
+ end
33
+ @writer.to_xml
34
+ end
35
+
36
+ private
37
+
38
+ def assign_ids!
39
+ walk_all(@document.packages)
40
+ walk_all(@document.classes)
41
+ walk_all(@document.enums)
42
+ walk_all(@document.data_types)
43
+ end
44
+
45
+ def walk_all(elements)
46
+ elements.each { |e| register!(e) }
47
+ end
48
+
49
+ def register!(element)
50
+ @id_gen.eaid_for(element)
51
+ case element
52
+ when Lutaml::Uml::Package then register_package!(element)
53
+ when Lutaml::Uml::UmlClass then register_class!(element)
54
+ end
55
+ end
56
+
57
+ def register_package!(pkg)
58
+ walk_all(pkg.packages)
59
+ walk_all(pkg.classes)
60
+ walk_all(pkg.enums)
61
+ walk_all(pkg.data_types)
62
+ end
63
+
64
+ def register_class!(klass)
65
+ klass.attributes.each { |a| @id_gen.eaid_for(a) }
66
+ klass.operations.each { |o| @id_gen.eaid_for(o) }
67
+ end
68
+
69
+ def emit_top_level
70
+ @document.packages.each { |p| emit_package(@writer, p) }
71
+ @document.classes.each { |c| emit_class(@writer, c) }
72
+ @document.enums.each { |e| emit_enum(@writer, e) }
73
+ @document.data_types.each { |d| emit_data_type(@writer, d) }
74
+ end
75
+
76
+ def emit_package(w, pkg)
77
+ w.packaged_element("uml:Package", @id_gen.eaid_for(pkg), pkg.name) do
78
+ pkg.packages.each { |p| emit_package(w, p) }
79
+ pkg.classes.each { |c| emit_class(w, c) }
80
+ pkg.enums.each { |e| emit_enum(w, e) }
81
+ pkg.data_types.each { |d| emit_data_type(w, d) }
82
+ end
83
+ end
84
+
85
+ def emit_class(w, klass)
86
+ w.packaged_element(
87
+ "uml:Class",
88
+ @id_gen.eaid_for(klass),
89
+ klass.name,
90
+ is_abstract: klass.is_abstract,
91
+ ) do
92
+ klass.attributes.each { |a| emit_attribute(w, a) }
93
+ klass.operations.each { |o| emit_operation(w, o) }
94
+ end
95
+ end
96
+
97
+ def emit_enum(w, enum)
98
+ w.packaged_element(
99
+ "uml:Enumeration",
100
+ @id_gen.eaid_for(enum),
101
+ enum.name,
102
+ ) do
103
+ (enum.values || []).each { |v| emit_literal(w, v) }
104
+ end
105
+ end
106
+
107
+ def emit_data_type(w, data_type)
108
+ w.packaged_element(
109
+ "uml:DataType",
110
+ @id_gen.eaid_for(data_type),
111
+ data_type.name,
112
+ ) do
113
+ data_type.attributes.each { |a| emit_attribute(w, a) }
114
+ data_type.operations.each { |o| emit_operation(w, o) }
115
+ end
116
+ end
117
+
118
+ def emit_attribute(w, attr)
119
+ type_ref = type_reference(attr.type)
120
+ w.owned_attribute(@id_gen.eaid_for(attr), attr.name, type: type_ref) do
121
+ # Multiplicity / defaults not modeled in Lutaml::Uml; deferred.
122
+ end
123
+ end
124
+
125
+ def emit_operation(w, op)
126
+ w.owned_operation(@id_gen.eaid_for(op), op.name)
127
+ end
128
+
129
+ def emit_literal(w, literal)
130
+ case literal
131
+ when Lutaml::Uml::Value
132
+ w.owned_literal(@id_gen.eaid_for(literal), literal.name)
133
+ when String
134
+ w.owned_literal(synthesize_literal_id(literal), literal)
135
+ else
136
+ w.owned_literal(synthesize_literal_id(literal.to_s), literal.to_s)
137
+ end
138
+ end
139
+
140
+ def type_reference(type_name)
141
+ return nil if type_name.nil? || type_name.empty?
142
+
143
+ type_name
144
+ end
145
+
146
+ def synthesize_literal_id(name)
147
+ format("EAID_LIT_%<hex>08X", hex: name.to_s.bytes.sum)
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ module Ea
6
+ module Transformers
7
+ module UmlToXmi
8
+ # Low-level XML structural primitives for emitting Sparx-flavored XMI.
9
+ #
10
+ # This class has no UML domain knowledge — it only knows the XML shapes
11
+ # (XMI root, uml:Model, packagedElement, ownedAttribute, …) required to
12
+ # produce a well-formed Sparx XMI document. {Transformer} drives it.
13
+ class Writer
14
+ XMI_NS = "http://www.omg.org/spec/XMI/20131001"
15
+ UML_NS = "http://www.omg.org/spec/UML/20161101"
16
+
17
+ attr_reader :builder
18
+
19
+ def initialize
20
+ @builder = Nokogiri::XML::Builder.new(encoding: "UTF-8")
21
+ end
22
+
23
+ def xmi_root
24
+ @builder["xmi"].XMI(root_attributes) { yield self }
25
+ end
26
+
27
+ def documentation(exporter: "ea-rb", exporter_version: Ea::VERSION)
28
+ @builder["xmi"].Documentation(
29
+ exporter: exporter,
30
+ exporterVersion: exporter_version,
31
+ )
32
+ end
33
+
34
+ def uml_model(id, name)
35
+ @builder["uml"].Model(
36
+ "xmi:type": "uml:Model",
37
+ "xmi:id": id,
38
+ name: name,
39
+ ) { yield self }
40
+ end
41
+
42
+ def packaged_element(uml_type, id, name, **attrs)
43
+ @builder.packagedElement(packaged_attrs(uml_type, id, name, attrs)) do
44
+ yield self if block_given?
45
+ end
46
+ end
47
+
48
+ def owned_attribute(id, name, type: nil, visibility: "public")
49
+ attrs = { "xmi:type": "uml:Property", "xmi:id": id, name: name,
50
+ visibility: visibility }
51
+ attrs[:type] = type if type
52
+ @builder.ownedAttribute(attrs) { yield self if block_given? }
53
+ end
54
+
55
+ def owned_operation(id, name, visibility: "public")
56
+ @builder.ownedOperation(
57
+ "xmi:type": "uml:Operation", "xmi:id": id, name: name,
58
+ visibility: visibility,
59
+ ) { yield self if block_given? }
60
+ end
61
+
62
+ def owned_literal(id, name)
63
+ @builder.ownedLiteral(
64
+ "xmi:type": "uml:EnumerationLiteral", "xmi:id": id, name: name,
65
+ )
66
+ end
67
+
68
+ def generalization(id, general_id)
69
+ @builder.generalization(
70
+ "xmi:type": "uml:Generalization",
71
+ "xmi:id": id,
72
+ general: general_id,
73
+ )
74
+ end
75
+
76
+ def to_xml
77
+ @builder.doc.to_xml
78
+ end
79
+
80
+ private
81
+
82
+ def root_attributes
83
+ { "xmlns:xmi": XMI_NS, "xmlns:uml": UML_NS }
84
+ end
85
+
86
+ def packaged_attrs(uml_type, id, name, attrs)
87
+ result = { "xmi:type": uml_type, "xmi:id": id }
88
+ result[:name] = name if name
89
+ result[:"isAbstract"] = "true" if attrs[:is_abstract]
90
+ result[:visibility] = attrs[:visibility] if attrs[:visibility]
91
+ result
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Transformers
5
+ # Lossy transformer: Lutaml::Uml::Document → Sparx XMI.
6
+ #
7
+ # Use this when the source is a tool-agnostic UML model (from LML,
8
+ # MagicDraw, Papyrus, etc.) and some information loss is acceptable.
9
+ # For Sparx QEA → Sparx XMI, use {Ea::Transformers::QeaToXmi} instead.
10
+ module UmlToXmi
11
+ autoload :Transformer, "ea/transformers/uml_to_xmi/transformer"
12
+ autoload :Writer, "ea/transformers/uml_to_xmi/writer"
13
+ autoload :IdGenerator, "ea/transformers/uml_to_xmi/id_generator"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ # Output transformers — converts domain models to interchange formats.
5
+ #
6
+ # Two entry points:
7
+ #
8
+ # {uml_to_xmi} — lossy, takes a Lutaml::Uml::Document (cross-tool use)
9
+ # {qea_to_xmi} — full fidelity, takes an Ea::Qea::Database
10
+ # (Sparx-to-Sparx round-trip)
11
+ module Transformers
12
+ autoload :UmlToXmi, "ea/transformers/uml_to_xmi"
13
+ autoload :QeaToXmi, "ea/transformers/qea_to_xmi"
14
+
15
+ class << self
16
+ # Lossy: any Lutaml::Uml::Document → Sparx XMI (cross-tool).
17
+ # @param document [Lutaml::Uml::Document]
18
+ # @return [String] XMI XML
19
+ def uml_to_xmi(document)
20
+ UmlToXmi::Transformer.new(document).serialize
21
+ end
22
+
23
+ # Full fidelity: Ea::Qea::Database → Sparx XMI.
24
+ # Walks the QEA tables directly — no intermediate UML model, no loss of
25
+ # Sparx-specific concepts (stereotypes, tagged values, multiplicities,
26
+ # diagrams, xrefs).
27
+ # @param database [Ea::Qea::Database]
28
+ # @return [String] XMI XML
29
+ def qea_to_xmi(database)
30
+ QeaToXmi::Transformer.new(database).serialize
31
+ end
32
+ end
33
+ end
34
+ end
data/lib/ea/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ea
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.4"
5
5
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Xmi
5
+ module LiquidDrops
6
+ class AssociationDrop < Liquid::Drop
7
+ def initialize(association, options = {}) # rubocop:disable Lint/MissingSuper
8
+ @model = association
9
+ @options = options
10
+ @lookup = options[:lookup]
11
+ end
12
+
13
+ def xmi_id
14
+ @model.xmi_id
15
+ end
16
+
17
+ def member_end
18
+ @model.member_end
19
+ end
20
+
21
+ def member_end_type
22
+ @model.member_end_type
23
+ end
24
+
25
+ def member_end_cardinality
26
+ ::Ea::Xmi::LiquidDrops::CardinalityDrop.new(@model.member_end_cardinality)
27
+ end
28
+
29
+ def member_end_attribute_name
30
+ @model.member_end_attribute_name
31
+ end
32
+
33
+ def member_end_xmi_id
34
+ @model.member_end_xmi_id
35
+ end
36
+
37
+ def owner_end
38
+ @model.owner_end
39
+ end
40
+
41
+ def owner_end_xmi_id
42
+ @model.owner_end_xmi_id
43
+ end
44
+
45
+ def definition
46
+ @model.definition
47
+ end
48
+
49
+ def connector
50
+ connector = @lookup.fetch_connector(@model.xmi_id)
51
+ ::Ea::Xmi::LiquidDrops::ConnectorDrop.new(connector, @options)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Xmi
5
+ module LiquidDrops
6
+ class AttributeDrop < Liquid::Drop
7
+ def initialize(model, options = {}) # rubocop:disable Lint/MissingSuper
8
+ @model = model
9
+ @options = options
10
+ @lookup = options[:lookup]
11
+ end
12
+
13
+ def id
14
+ @model.id
15
+ end
16
+
17
+ def name
18
+ @model.name
19
+ end
20
+
21
+ def type
22
+ @model.type
23
+ end
24
+
25
+ def xmi_id
26
+ @model.xmi_id
27
+ end
28
+
29
+ def is_derived # rubocop:disable Naming/PredicateName,Naming/PredicatePrefix
30
+ @model.is_derived
31
+ end
32
+
33
+ def cardinality
34
+ ::Ea::Xmi::LiquidDrops::CardinalityDrop.new(@model.cardinality)
35
+ end
36
+
37
+ def definition
38
+ if @options[:with_assoc] && @model.association
39
+ @lookup.loopup_assoc_def(@model.association)
40
+ else
41
+ @model.definition
42
+ end
43
+ end
44
+
45
+ def association
46
+ if @options[:with_assoc] && @model.association
47
+ @model.association
48
+ end
49
+ end
50
+
51
+ def association_connector
52
+ return unless @model.association
53
+
54
+ connector = @lookup.fetch_connector(@model.association)
55
+ if connector
56
+ ::Ea::Xmi::LiquidDrops::ConnectorDrop.new(connector, @options)
57
+ end
58
+ end
59
+
60
+ def type_ns
61
+ if @options[:with_assoc] && @model.association
62
+ @model.type_ns
63
+ end
64
+ end
65
+
66
+ def stereotype
67
+ @lookup.doc_node_attribute_value(@model.xmi_id, "stereotype")
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Xmi
5
+ module LiquidDrops
6
+ class CardinalityDrop < Liquid::Drop
7
+ def initialize(model) # rubocop:disable Lint/MissingSuper
8
+ @model = model
9
+ end
10
+
11
+ def min
12
+ return nil unless @model
13
+
14
+ case @model
15
+ when ::Lutaml::Uml::Cardinality
16
+ @model.min
17
+ else
18
+ @model.lower_value&.value
19
+ end
20
+ end
21
+
22
+ def max
23
+ return nil unless @model
24
+
25
+ case @model
26
+ when ::Lutaml::Uml::Cardinality
27
+ @model.max
28
+ else
29
+ @model.upper_value&.value
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Xmi
5
+ module LiquidDrops
6
+ class ConnectorDrop < Liquid::Drop
7
+ def initialize(model, options = {}) # rubocop:disable Lint/MissingSuper
8
+ @model = model
9
+ @options = options
10
+ @id_name_mapping = options[:id_name_mapping]
11
+ end
12
+
13
+ def idref
14
+ @model.idref
15
+ end
16
+
17
+ def name
18
+ @model.name
19
+ end
20
+
21
+ def type
22
+ @model&.properties&.ea_type
23
+ end
24
+
25
+ def documentation
26
+ @model&.documentation&.value
27
+ end
28
+
29
+ def ea_type
30
+ @model&.properties&.ea_type
31
+ end
32
+
33
+ def direction
34
+ @model&.properties&.direction
35
+ end
36
+
37
+ def source
38
+ ::Ea::Xmi::LiquidDrops::SourceTargetDrop.new(@model.source,
39
+ @options)
40
+ end
41
+
42
+ def target
43
+ ::Ea::Xmi::LiquidDrops::SourceTargetDrop.new(@model.target,
44
+ @options)
45
+ end
46
+
47
+ def recognized?
48
+ !!@id_name_mapping[@model.source.idref] &&
49
+ !!@id_name_mapping[@model.target.idref]
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Xmi
5
+ module LiquidDrops
6
+ class ConstraintDrop < Liquid::Drop
7
+ def initialize(model) # rubocop:disable Lint/MissingSuper
8
+ @model = model
9
+ end
10
+
11
+ def name
12
+ @model.name
13
+ end
14
+
15
+ def type
16
+ @model.type
17
+ end
18
+
19
+ def weight
20
+ @model.weight
21
+ end
22
+
23
+ def status
24
+ @model.status
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end