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,323 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Diagram
5
+ module ElementRenderers
6
+ # Renderer for UML class elements
7
+ class ClassRenderer < BaseRenderer
8
+ def render_shape(style) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
9
+ x = element[:x] || 0
10
+ y = element[:y] || 0
11
+ width = element[:width] || 120
12
+ height = element[:height] || 80
13
+
14
+ # Calculate compartment heights
15
+ name_height = 25
16
+ attributes_height = calculate_attributes_height
17
+ operations_height = calculate_operations_height
18
+
19
+ total_height = name_height + attributes_height + operations_height
20
+
21
+ # Adjust element height if needed
22
+ height = [height, total_height].max
23
+
24
+ # Build style string for the group
25
+ group_style_attrs = []
26
+ group_style_attrs << "stroke-width:#{style[:stroke_width] || 1}"
27
+ group_style_attrs << "stroke-linecap:#{style[:stroke_linecap] ||
28
+ 'round'}"
29
+ group_style_attrs << "stroke-linejoin:#{style[:stroke_linejoin] ||
30
+ 'bevel'}"
31
+ group_style_attrs << "fill:#{style[:fill]}"
32
+ group_style_attrs << "fill-opacity:#{style[:fill_opacity] ||
33
+ '1.00'}"
34
+ group_style_attrs << "stroke:#{style[:stroke]}"
35
+ group_style_attrs << "stroke-opacity:#{style[:stroke_opacity] ||
36
+ '1.00'}"
37
+
38
+ # Build style string for compartment lines
39
+ line_style_attrs = []
40
+ line_style_attrs << "stroke-width:#{style[:compartment_width] || 1}"
41
+ line_style_attrs << "stroke-linecap:#{style[:stroke_linecap] ||
42
+ 'round'}"
43
+ line_style_attrs << "stroke-linejoin:#{style[:stroke_linejoin] ||
44
+ 'bevel'}"
45
+ line_style_attrs << "fill:#000000"
46
+ line_style_attrs << "fill-opacity:0.00"
47
+ line_style_attrs << "stroke:#{style[:stroke] || '#000000'}"
48
+ line_style_attrs << "stroke-opacity:#{style[:stroke_opacity] ||
49
+ '1.00'}"
50
+
51
+ <<~SVG
52
+ <g style="#{group_style_attrs.join(';')}">
53
+ <rect x="#{x}"
54
+ y="#{y}"
55
+ width="#{width}"
56
+ height="#{height}"
57
+ rx="#{style[:corner_radius] || 0.00}"
58
+ shape-rendering="#{style[:shape_rendering] || 'auto'}" />
59
+ </g>
60
+ <g style="#{line_style_attrs.join(';')}">
61
+ <!-- Name compartment -->
62
+ <path d="M #{x} #{y + name_height} L #{x + width} #{y + name_height}" shape-rendering="#{style[:shape_rendering] || 'auto'}"/>
63
+
64
+ <!-- Attributes compartment (if any) -->
65
+ #{render_attributes_compartment_separator(x, y, width, name_height, attributes_height, style)}
66
+
67
+ <!-- Operations compartment (if any) -->
68
+ #{render_operations_compartment_separator(x, y, width, name_height, attributes_height, operations_height, style)}
69
+ </g>
70
+ SVG
71
+ end
72
+
73
+ def render_label(style) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
74
+ x = element[:x] || 0
75
+ y = element[:y] || 0
76
+ width = element[:width] || 120
77
+
78
+ # Calculate compartment heights
79
+ name_height = 25
80
+ attributes_height = calculate_attributes_height
81
+ calculate_operations_height
82
+
83
+ svg_content = +""
84
+
85
+ # Class name (centered in name compartment)
86
+ name_y = y + (name_height / 2) + 5
87
+ svg_content << render_text_element(
88
+ element[:name],
89
+ x + (width / 2),
90
+ name_y,
91
+ style,
92
+ "lutaml-diagram-class-name",
93
+ font_weight: style[:font_weight] || "700",
94
+ font_style: style[:font_style] || "italic",
95
+ font_family: style[:font_family] || "Cambria",
96
+ font_size: style[:font_size] || "7pt",
97
+ )
98
+
99
+ # Stereotype (above name if present)
100
+ if element[:stereotype]
101
+ stereotype_y = y + 15
102
+ svg_content << render_text_element(
103
+ "«#{element[:stereotype]}»",
104
+ x + (width / 2),
105
+ stereotype_y,
106
+ style,
107
+ "lutaml-diagram-class-stereotype",
108
+ font_weight: style[:stereotype_font_weight] || "0",
109
+ font_style: style[:stereotype_font_style] || "normal",
110
+ font_family: style[:stereotype_font_family] || "Cambria",
111
+ font_size: style[:stereotype_font_size] || "7pt",
112
+ )
113
+ end
114
+
115
+ # Attributes
116
+ if element[:attributes]&.any?
117
+ attr_y = y + name_height + 15
118
+ element[:attributes].each do |attr|
119
+ svg_content << render_text_element(
120
+ format_attribute(attr),
121
+ x + 5,
122
+ attr_y,
123
+ style,
124
+ "lutaml-diagram-class-attribute",
125
+ font_weight: style[:attribute_font_weight] || "0",
126
+ font_style: style[:attribute_font_style] || "normal",
127
+ font_family: style[:attribute_font_family] || "Cambria",
128
+ font_size: style[:attribute_font_size] || "7pt",
129
+ text_anchor: "start",
130
+ )
131
+ attr_y += 15
132
+ end
133
+ end
134
+
135
+ # Operations
136
+ if element[:operations]&.any?
137
+ op_y = y + name_height + attributes_height + 15
138
+ element[:operations].each do |op|
139
+ svg_content << render_text_element(
140
+ format_operation(op),
141
+ x + 5,
142
+ op_y,
143
+ style,
144
+ "lutaml-diagram-class-operation",
145
+ font_weight: style[:attribute_font_weight] || "0",
146
+ font_style: style[:attribute_font_style] || "normal",
147
+ font_family: style[:attribute_font_family] || "Cambria",
148
+ font_size: style[:attribute_font_size] || "7pt",
149
+ text_anchor: "start",
150
+ )
151
+ op_y += 15
152
+ end
153
+ end
154
+
155
+ # Build text style string
156
+ text_style_attrs = []
157
+ text_style_attrs << "stroke-width:1"
158
+ text_style_attrs << "stroke-linecap:round"
159
+ text_style_attrs << "stroke-linejoin:bevel"
160
+ text_style_attrs << "fill:#000000"
161
+ text_style_attrs << "fill-opacity:1.00"
162
+ text_style_attrs << "stroke:#000000"
163
+ text_style_attrs << "stroke-opacity:0.00"
164
+
165
+ "<g style=\"#{text_style_attrs.join(';')}\">\n#{svg_content}\n</g>"
166
+ end
167
+
168
+
169
+ def calculate_attributes_height
170
+ return 0 unless element[:attributes]&.any?
171
+
172
+ (element[:attributes].size * 15) + 10
173
+ end
174
+
175
+ def calculate_operations_height
176
+ return 0 unless element[:operations]&.any?
177
+
178
+ (element[:operations].size * 15) + 10
179
+ end
180
+
181
+ def render_attributes_compartment_separator( # rubocop:disable Metrics/ParameterLists
182
+ x, y, width, name_height, # rubocop:disable Naming/MethodParameterName
183
+ attributes_height, style
184
+ )
185
+ return "" if attributes_height.zero?
186
+
187
+ separator_y = y + name_height + attributes_height
188
+ <<~SVG
189
+ <path d="M #{x} #{separator_y} L #{x + width} #{separator_y}" shape-rendering="#{style[:shape_rendering] || 'auto'}"/>
190
+ SVG
191
+ end
192
+
193
+ def render_operations_compartment_separator( # rubocop:disable Metrics/ParameterLists
194
+ x, y, width, name_height, # rubocop:disable Naming/MethodParameterName
195
+ attributes_height, operations_height, style
196
+ )
197
+ return "" if operations_height.zero?
198
+
199
+ separator_y = y + name_height +
200
+ attributes_height + operations_height
201
+ <<~SVG
202
+ <path d="M #{x} #{separator_y} L #{x + width} #{separator_y}" shape-rendering="#{style[:shape_rendering] || 'auto'}"/>
203
+ SVG
204
+ end
205
+
206
+ def render_text_element(text, x, y, style, css_class, options = {}) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity,Metrics/ParameterLists,Naming/MethodParameterName
207
+ return "" unless text
208
+
209
+ font_size = options[:font_size] || style[:font_size] || "7pt"
210
+ font_weight = options[:font_weight] || style[:font_weight] || "0"
211
+ font_style = options[:font_style] || style[:font_style] || "normal"
212
+ font_family = options[:font_family] || style[:font_family] ||
213
+ "Cambria"
214
+ fill_color = options[:fill] || style[:text_color] || "#000000"
215
+ text_anchor = options[:text_anchor] || "middle"
216
+
217
+ # Calculate text length approximation
218
+ # (EA uses this for precise positioning)
219
+ text_length = calculate_text_length(text, font_size)
220
+
221
+ # Build transform attribute if needed
222
+ transform_attr = ""
223
+ if options[:rotate] && options[:rotate] != 0
224
+ transform_attr = "transform=\"rotate(#{options[:rotate]} " \
225
+ "#{x} #{y})\""
226
+ end
227
+
228
+ # Text stroke styling (EA uses this for text elements)
229
+ text_stroke = options[:text_stroke] ||
230
+ style[:text_stroke] ||
231
+ "#000000"
232
+ text_stroke_opacity = options[:text_stroke_opacity] ||
233
+ style[:text_stroke_opacity] || "0.00"
234
+ text_stroke_width = options[:text_stroke_width] ||
235
+ style[:text_stroke_width] || "0"
236
+
237
+ <<~SVG
238
+ <text x="#{x}.00"
239
+ y="#{y}.00"
240
+ text-anchor="#{text_anchor}"
241
+ class="#{css_class}"
242
+ #{"textLength=\"#{text_length}\"" if text_length.positive?}
243
+ style="font-family:#{font_family}; font-weight:#{font_weight}; font-style:#{font_style}; font-size:#{font_size}; fill:#{fill_color};fill-opacity:1.00; stroke:#{text_stroke}; stroke-opacity:#{text_stroke_opacity} stroke-width:#{text_stroke_width}; white-space: pre;"
244
+ xml:space="preserve"
245
+ #{transform_attr}>
246
+ #{escape_text(text)}
247
+ </text>
248
+ SVG
249
+ end
250
+
251
+ def format_attribute(attribute)
252
+ return attribute unless attribute.is_a?(Hash)
253
+
254
+ visibility = visibility_symbol(attribute[:visibility])
255
+ name = attribute[:name] || ""
256
+ type = attribute[:type] ? ": #{attribute[:type]}" : ""
257
+
258
+ "#{visibility}#{name}#{type}"
259
+ end
260
+
261
+ def format_operation(operation)
262
+ return operation unless operation.is_a?(Hash)
263
+
264
+ visibility = visibility_symbol(operation[:visibility])
265
+ name = operation[:name] || ""
266
+ params = format_parameters(operation[:parameters] || [])
267
+ return_type = if operation[:return_type]
268
+ ": #{operation[:return_type]}"
269
+ else
270
+ ""
271
+ end
272
+
273
+ "#{visibility}#{name}(#{params})#{return_type}"
274
+ end
275
+
276
+ def format_parameters(parameters)
277
+ parameters.map do |param|
278
+ if param.is_a?(Hash)
279
+ "#{param[:name]}: #{param[:type]}"
280
+ else
281
+ param.to_s
282
+ end
283
+ end.join(", ")
284
+ end
285
+
286
+ def visibility_symbol(visibility)
287
+ case visibility&.to_s
288
+ when "public" then "+"
289
+ when "private" then "-"
290
+ when "protected" then "#"
291
+ when "package" then "~"
292
+ else ""
293
+ end
294
+ end
295
+
296
+ # Parse font size string to get numeric value
297
+ def parse_font_size(font_size)
298
+ return 7 if font_size.nil? # default size
299
+
300
+ # Extract numeric part from font size string (e.g., "7pt" -> 7)
301
+ if font_size.is_a?(String)
302
+ match = font_size.match(/(\d+(?:\.\d+)?)/)
303
+ match ? match[1].to_f : 7
304
+ else
305
+ font_size.to_f
306
+ end
307
+ end
308
+
309
+ # Calculate approximate text length in pixels
310
+ # (EA uses this for precise positioning)
311
+ def calculate_text_length(text, font_size)
312
+ return 0 unless text
313
+
314
+ # Simple approximation: average character width * text length
315
+ # This is a rough approximation
316
+ # - in a real implementation, we'd use actual font metrics
317
+ font_size_num = parse_font_size(font_size)
318
+ text.to_s.length * (font_size_num * 0.6).to_i
319
+ end
320
+ end
321
+ end
322
+ end
323
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Diagram
5
+ module ElementRenderers
6
+ # Renderer for connector elements (relationships)
7
+ class ConnectorRenderer < BaseRenderer
8
+ attr_reader :source_element, :target_element
9
+
10
+ def initialize(connector, style_resolver, source_element = nil,
11
+ target_element = nil)
12
+ super(connector, style_resolver)
13
+ @source_element = source_element
14
+ @target_element = target_element
15
+ end
16
+
17
+ # Render the connector as SVG path
18
+ # @return [String] SVG content for the connector
19
+ def render
20
+ path_builder = PathBuilder.new(element, source_element,
21
+ target_element)
22
+ path_data = path_builder.build_path
23
+
24
+ style = style_resolver.resolve_connector_style(element)
25
+
26
+ css_classes = ["lutaml-diagram-connector",
27
+ "lutaml-diagram-connector-#{element[:type]}"]
28
+
29
+ <<~SVG
30
+ <path d="#{path_data}"
31
+ class="#{css_classes.join(' ')}"
32
+ style="#{style_to_css(style)}"
33
+ data-connector-id="#{element[:id]}"
34
+ data-connector-type="#{element[:type]}"
35
+ marker-end="url(#arrowhead)" />
36
+ SVG
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Diagram
5
+ module ElementRenderers
6
+ # Renderer for UML package elements
7
+ class PackageRenderer < BaseRenderer
8
+ def render_shape(style) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
9
+ x = element[:x] || 0
10
+ y = element[:y] || 0
11
+ width = element[:width] || 120
12
+ height = element[:height] || 80
13
+
14
+ # Package tab height
15
+ tab_height = 20
16
+
17
+ # Draw package shape with tab
18
+ <<~SVG
19
+ <!-- Main package body -->
20
+ <polygon points="#{x},#{y + tab_height} #{x + width},#{y + tab_height} #{x + width},#{y + height} #{x},#{y + height}"
21
+ fill="#{style[:fill]}"
22
+ stroke="#{style[:stroke]}"
23
+ stroke-width="#{style[:stroke_width] || 2}"
24
+ class="lutaml-diagram-package-shape" />
25
+
26
+ <!-- Package tab -->
27
+ <polygon points="#{x + 10},#{y + tab_height} #{x + 50},#{y + tab_height} #{x + 50},#{y} #{x + 10},#{y}"
28
+ fill="#{style[:fill]}"
29
+ stroke="#{style[:stroke]}"
30
+ stroke-width="#{style[:stroke_width] || 2}"
31
+ class="lutaml-diagram-package-tab" />
32
+ SVG
33
+ end
34
+
35
+ def render_label(style) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
36
+ x = element[:x] || 0
37
+ y = element[:y] || 0
38
+
39
+ # Package name positioned in the tab area
40
+ tab_height = 20
41
+ text_x = x + 30 # Center in the tab
42
+ text_y = y + (tab_height / 2) + 5
43
+
44
+ <<~SVG
45
+ <text x="#{text_x}"
46
+ y="#{text_y}"
47
+ text-anchor="middle"
48
+ dominant-baseline="middle"
49
+ font-family="#{style[:font_family]}"
50
+ font-size="#{style[:font_size]}"
51
+ font-weight="#{style[:font_weight] || 'bold'}"
52
+ fill="#{style[:text_color] || '#000000'}"
53
+ class="lutaml-diagram-package-name">
54
+ #{escape_text(element[:name])}
55
+ </text>
56
+ SVG
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Diagram
5
+ module ElementRenderers
6
+ autoload :BaseRenderer,
7
+ "ea/diagram/element_renderers/base_renderer"
8
+ autoload :ClassRenderer,
9
+ "ea/diagram/element_renderers/class_renderer"
10
+ autoload :PackageRenderer,
11
+ "ea/diagram/element_renderers/package_renderer"
12
+ autoload :ConnectorRenderer,
13
+ "ea/diagram/element_renderers/connector_renderer"
14
+
15
+ # Registry for element type to renderer class mapping.
16
+ # New element types can be added without modifying SvgRenderer.
17
+ class RendererRegistry
18
+ def initialize
19
+ @renderers = {}
20
+ end
21
+
22
+ def register(element_type, renderer_class)
23
+ @renderers[element_type.to_s] = renderer_class
24
+ end
25
+
26
+ def renderer_for(element_type)
27
+ @renderers[element_type.to_s]
28
+ end
29
+
30
+ def registered?(element_type)
31
+ @renderers.key?(element_type.to_s)
32
+ end
33
+ end
34
+
35
+ # Default registry with built-in renderers
36
+ DEFAULT_REGISTRY = RendererRegistry.new.tap do |r|
37
+ r.register("class", ClassRenderer)
38
+ r.register("datatype", ClassRenderer)
39
+ r.register("package", PackageRenderer)
40
+ end
41
+ end
42
+ end
43
+ end