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.
- checksums.yaml +4 -4
- data/CLAUDE.md +125 -0
- data/Rakefile +12 -4
- data/TODO.next/00-publish-blocking-bugs.md +74 -0
- data/TODO.next/01-standalone-ea-gem-identity.md +76 -0
- data/TODO.next/02-optional-lutaml-uml-dependency.md +47 -0
- data/TODO.next/03-slim-lutaml-uml.md +79 -0
- data/TODO.next/04-loader-registry-for-uml-repository.md +49 -0
- data/TODO.next/05-extract-shared-transformer-methods.md +14 -0
- data/TODO.next/06-deduplicate-stereotype-loading.md +17 -0
- data/TODO.next/07-transformer-registry-in-factory.md +20 -0
- data/TODO.next/08-connector-type-registry.md +27 -0
- data/TODO.next/09-element-renderer-registry.md +29 -0
- data/TODO.next/10-connector-renderer-lsp.md +18 -0
- data/TODO.next/11-consolidate-style-knowledge.md +33 -0
- data/TODO.next/12-data-driven-from-db-row.md +24 -0
- data/TODO.next/13-extract-duplicated-methods.md +17 -0
- data/TODO.next/14-remove-dead-code.md +10 -0
- data/TODO.next/15-narrow-exception-handling.md +39 -0
- data/TODO.next/16-repository-indexes.md +28 -0
- data/TODO.next/17-fix-spec-quality-and-coverage.md +32 -0
- data/TODO.next/18-xmi-tool-specific-parser-architecture.md +172 -0
- data/TODO.next/19-fix-ea-gemspec-dependency-declarations.md +56 -0
- data/TODO.next/20-ci-requires-unreleased-lutaml-uml.md +63 -0
- data/TODO.next/21-qeatoxmi-via-xmi-gem.md +340 -0
- data/TODO.next/22-strip-respond-to-from-qeatoxmi-specs.md +32 -0
- data/TODO.next/23-cleanup-idallocator.md +41 -0
- data/TODO.next/24-tighten-parity-specs.md +42 -0
- data/TODO.next/25-sparx-eaid-format-for-synthesized-ids.md +62 -0
- data/TODO.next/26-fix-uppervalue-lowervalue-count-gap.md +51 -0
- data/TODO.next/27-extract-cardinality-module.md +68 -0
- data/TODO.next/28-extract-xml-sanitizer.md +51 -0
- data/TODO.next/29-ocp-registry-for-classifier-builders.md +58 -0
- data/TODO.next/30-struct-return-for-association-end.md +37 -0
- data/TODO.next/31-idallocator-specs.md +27 -0
- data/TODO.next/32-phase2-gap-sentinel-specs.md +53 -0
- data/TODO.next/33-normalize-lower-cleanup.md +30 -0
- data/TODO.next/34-document-member-end-order-rt-prefix.md +29 -0
- data/TODO.next/35-walk-runstate-for-instance-slots.md +76 -0
- data/TODO.next/36-wire-interface-realization.md +50 -0
- data/TODO.next/37-visibility-returns-real-booleans.md +36 -0
- data/config/diagram_styles.yml +200 -0
- data/config/model_transformations.yml +266 -0
- data/config/qea_schema.yml +1024 -0
- data/docs/ea_to_uml_type_mapping.md +89 -0
- data/docs/xmi_qea_conversion_capabilities.md +99 -0
- data/examples/lur/20251010_current_plateau_v5.1.lur +0 -0
- data/examples/lur/basic.lur +0 -0
- data/examples/lur/test-output.lur +0 -0
- data/examples/lur/test.lur +0 -0
- data/examples/lur_basic_usage.rb +221 -0
- data/examples/lur_cli_workflow.rb +263 -0
- data/examples/lur_statistics.rb +326 -0
- data/examples/qea/20251010_current_plateau_v5.1.qea +0 -0
- data/examples/qea/ArcGISWorkspace_template.qea +0 -0
- data/examples/qea/README_qea_parser.adoc +230 -0
- data/examples/qea/UmlModel_template.qea +0 -0
- data/examples/qea/basic.qea +0 -0
- data/examples/qea/simple.qea +0 -0
- data/examples/qea/simple_example.qea +0 -0
- data/examples/qea/test.qea +0 -0
- data/examples/qea_standalone_query.rb +73 -0
- data/examples/qea_to_repository.rb +51 -0
- data/examples/smoke_test_real_qea.rb +81 -0
- data/exe/ea +7 -0
- data/lib/ea/cli/app.rb +72 -0
- data/lib/ea/cli/command/base.rb +80 -0
- data/lib/ea/cli/command/convert.rb +62 -0
- data/lib/ea/cli/command/diagrams.rb +81 -0
- data/lib/ea/cli/command/list.rb +61 -0
- data/lib/ea/cli/command/parse.rb +29 -0
- data/lib/ea/cli/command/stats.rb +20 -0
- data/lib/ea/cli/command/validate.rb +41 -0
- data/lib/ea/cli/command.rb +15 -0
- data/lib/ea/cli/error.rb +34 -0
- data/lib/ea/cli/output/formatter.rb +34 -0
- data/lib/ea/cli/output/json_formatter.rb +20 -0
- data/lib/ea/cli/output/table_formatter.rb +42 -0
- data/lib/ea/cli/output/yaml_formatter.rb +20 -0
- data/lib/ea/cli/output.rb +56 -0
- data/lib/ea/cli.rb +17 -0
- data/lib/ea/diagram/configuration.rb +379 -0
- data/lib/ea/diagram/element_renderers/base_renderer.rb +77 -0
- data/lib/ea/diagram/element_renderers/class_renderer.rb +323 -0
- data/lib/ea/diagram/element_renderers/connector_renderer.rb +41 -0
- data/lib/ea/diagram/element_renderers/package_renderer.rb +61 -0
- data/lib/ea/diagram/element_renderers.rb +43 -0
- data/lib/ea/diagram/extractor.rb +560 -0
- data/lib/ea/diagram/layout_engine.rb +170 -0
- data/lib/ea/diagram/path_builder.rb +202 -0
- data/lib/ea/diagram/style_parser.rb +42 -0
- data/lib/ea/diagram/style_resolver.rb +276 -0
- data/lib/ea/diagram/svg_renderer.rb +274 -0
- data/lib/ea/diagram/util.rb +73 -0
- data/lib/ea/diagram.rb +47 -0
- data/lib/ea/qea/benchmark.rb +210 -0
- data/lib/ea/qea/database.rb +308 -0
- data/lib/ea/qea/factory/association_builder.rb +203 -0
- data/lib/ea/qea/factory/association_transformer.rb +91 -0
- data/lib/ea/qea/factory/attribute_tag_transformer.rb +57 -0
- data/lib/ea/qea/factory/attribute_transformer.rb +93 -0
- data/lib/ea/qea/factory/base_transformer.rb +177 -0
- data/lib/ea/qea/factory/class_transformer.rb +116 -0
- data/lib/ea/qea/factory/constraint_transformer.rb +75 -0
- data/lib/ea/qea/factory/data_type_transformer.rb +77 -0
- data/lib/ea/qea/factory/diagram_transformer.rb +157 -0
- data/lib/ea/qea/factory/document_builder.rb +283 -0
- data/lib/ea/qea/factory/ea_to_uml_factory.rb +229 -0
- data/lib/ea/qea/factory/enum_transformer.rb +74 -0
- data/lib/ea/qea/factory/generalization_builder.rb +227 -0
- data/lib/ea/qea/factory/generalization_transformer.rb +98 -0
- data/lib/ea/qea/factory/instance_transformer.rb +68 -0
- data/lib/ea/qea/factory/object_property_transformer.rb +58 -0
- data/lib/ea/qea/factory/operation_transformer.rb +66 -0
- data/lib/ea/qea/factory/package_transformer.rb +145 -0
- data/lib/ea/qea/factory/reference_resolver.rb +99 -0
- data/lib/ea/qea/factory/stereotype_loader.rb +39 -0
- data/lib/ea/qea/factory/tagged_value_transformer.rb +38 -0
- data/lib/ea/qea/factory/transformer_registry.rb +80 -0
- data/lib/ea/qea/factory.rb +37 -0
- data/lib/ea/qea/file_detector.rb +178 -0
- data/lib/ea/qea/infrastructure/database_connection.rb +100 -0
- data/lib/ea/qea/infrastructure/schema_reader.rb +136 -0
- data/lib/ea/qea/infrastructure/table_reader.rb +224 -0
- data/lib/ea/qea/infrastructure.rb +12 -0
- data/lib/ea/qea/models/base_model.rb +59 -0
- data/lib/ea/qea/models/ea_attribute.rb +109 -0
- data/lib/ea/qea/models/ea_attribute_tag.rb +100 -0
- data/lib/ea/qea/models/ea_complexity_type.rb +79 -0
- data/lib/ea/qea/models/ea_connector.rb +160 -0
- data/lib/ea/qea/models/ea_connector_type.rb +60 -0
- data/lib/ea/qea/models/ea_constraint_type.rb +63 -0
- data/lib/ea/qea/models/ea_datatype.rb +104 -0
- data/lib/ea/qea/models/ea_diagram.rb +115 -0
- data/lib/ea/qea/models/ea_diagram_link.rb +78 -0
- data/lib/ea/qea/models/ea_diagram_object.rb +73 -0
- data/lib/ea/qea/models/ea_diagram_type.rb +56 -0
- data/lib/ea/qea/models/ea_document.rb +63 -0
- data/lib/ea/qea/models/ea_object.rb +223 -0
- data/lib/ea/qea/models/ea_object_constraint.rb +53 -0
- data/lib/ea/qea/models/ea_object_property.rb +87 -0
- data/lib/ea/qea/models/ea_object_type.rb +73 -0
- data/lib/ea/qea/models/ea_operation.rb +127 -0
- data/lib/ea/qea/models/ea_operation_param.rb +76 -0
- data/lib/ea/qea/models/ea_package.rb +78 -0
- data/lib/ea/qea/models/ea_script.rb +62 -0
- data/lib/ea/qea/models/ea_status_type.rb +66 -0
- data/lib/ea/qea/models/ea_stereotype.rb +57 -0
- data/lib/ea/qea/models/ea_tagged_value.rb +99 -0
- data/lib/ea/qea/models/ea_xref.rb +165 -0
- data/lib/ea/qea/models.rb +35 -0
- data/lib/ea/qea/repositories/base_repository.rb +225 -0
- data/lib/ea/qea/repositories/object_repository.rb +219 -0
- data/lib/ea/qea/repositories.rb +10 -0
- data/lib/ea/qea/services/configuration.rb +211 -0
- data/lib/ea/qea/services/database_loader.rb +191 -0
- data/lib/ea/qea/services.rb +10 -0
- data/lib/ea/qea/validation/association_validator.rb +73 -0
- data/lib/ea/qea/validation/attribute_validator.rb +91 -0
- data/lib/ea/qea/validation/base_validator.rb +331 -0
- data/lib/ea/qea/validation/class_validator.rb +121 -0
- data/lib/ea/qea/validation/database/circular_reference_validator.rb +109 -0
- data/lib/ea/qea/validation/database/orphan_validator.rb +153 -0
- data/lib/ea/qea/validation/database/referential_integrity_validator.rb +128 -0
- data/lib/ea/qea/validation/database.rb +16 -0
- data/lib/ea/qea/validation/diagram_validator.rb +112 -0
- data/lib/ea/qea/validation/formatters/json_formatter.rb +137 -0
- data/lib/ea/qea/validation/formatters/text_formatter.rb +235 -0
- data/lib/ea/qea/validation/formatters.rb +12 -0
- data/lib/ea/qea/validation/operation_validator.rb +71 -0
- data/lib/ea/qea/validation/package_validator.rb +111 -0
- data/lib/ea/qea/validation/validation_engine.rb +387 -0
- data/lib/ea/qea/validation/validation_message.rb +144 -0
- data/lib/ea/qea/validation/validation_result.rb +210 -0
- data/lib/ea/qea/validation/validator_registry.rb +134 -0
- data/lib/ea/qea/validation.rb +28 -0
- data/lib/ea/qea/verification/comparison_result.rb +264 -0
- data/lib/ea/qea/verification/document_normalizer.rb +169 -0
- data/lib/ea/qea/verification/document_verifier.rb +322 -0
- data/lib/ea/qea/verification/element_comparator.rb +277 -0
- data/lib/ea/qea/verification/structure_matcher.rb +287 -0
- data/lib/ea/qea/verification.rb +14 -0
- data/lib/ea/qea.rb +185 -0
- data/lib/ea/transformations/configuration.rb +333 -0
- data/lib/ea/transformations/format_registry.rb +366 -0
- data/lib/ea/transformations/parsers/base_parser.rb +482 -0
- data/lib/ea/transformations/parsers/qea_parser.rb +401 -0
- data/lib/ea/transformations/parsers/xmi_parser.rb +243 -0
- data/lib/ea/transformations/transformation_engine.rb +390 -0
- data/lib/ea/transformations.rb +85 -0
- data/lib/ea/transformers/qea_to_xmi/association_end.rb +19 -0
- data/lib/ea/transformers/qea_to_xmi/cardinality.rb +96 -0
- data/lib/ea/transformers/qea_to_xmi/context.rb +106 -0
- data/lib/ea/transformers/qea_to_xmi/guid_format.rb +56 -0
- data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +92 -0
- data/lib/ea/transformers/qea_to_xmi/run_state.rb +107 -0
- data/lib/ea/transformers/qea_to_xmi/transformer.rb +607 -0
- data/lib/ea/transformers/qea_to_xmi/visibility.rb +73 -0
- data/lib/ea/transformers/qea_to_xmi.rb +29 -0
- data/lib/ea/transformers/uml_to_xmi/id_generator.rb +54 -0
- data/lib/ea/transformers/uml_to_xmi/transformer.rb +152 -0
- data/lib/ea/transformers/uml_to_xmi/writer.rb +96 -0
- data/lib/ea/transformers/uml_to_xmi.rb +16 -0
- data/lib/ea/transformers.rb +34 -0
- data/lib/ea/version.rb +1 -1
- data/lib/ea/xmi/liquid_drops/association_drop.rb +56 -0
- data/lib/ea/xmi/liquid_drops/attribute_drop.rb +72 -0
- data/lib/ea/xmi/liquid_drops/cardinality_drop.rb +35 -0
- data/lib/ea/xmi/liquid_drops/connector_drop.rb +54 -0
- data/lib/ea/xmi/liquid_drops/constraint_drop.rb +29 -0
- data/lib/ea/xmi/liquid_drops/data_type_drop.rb +63 -0
- data/lib/ea/xmi/liquid_drops/dependency_drop.rb +36 -0
- data/lib/ea/xmi/liquid_drops/diagram_drop.rb +34 -0
- data/lib/ea/xmi/liquid_drops/enum_drop.rb +49 -0
- data/lib/ea/xmi/liquid_drops/enum_owned_literal_drop.rb +25 -0
- data/lib/ea/xmi/liquid_drops/generalization_attribute_drop.rb +87 -0
- data/lib/ea/xmi/liquid_drops/generalization_drop.rb +127 -0
- data/lib/ea/xmi/liquid_drops/klass_drop.rb +191 -0
- data/lib/ea/xmi/liquid_drops/operation_drop.rb +29 -0
- data/lib/ea/xmi/liquid_drops/package_drop.rb +108 -0
- data/lib/ea/xmi/liquid_drops/root_drop.rb +34 -0
- data/lib/ea/xmi/liquid_drops/source_target_drop.rb +43 -0
- data/lib/ea/xmi/lookup_service.rb +89 -0
- data/lib/ea/xmi/parser.rb +919 -0
- data/lib/ea/xmi.rb +35 -0
- data/lib/ea.rb +10 -1
- 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
|