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,274 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Diagram
|
|
5
|
+
# Main SVG renderer for EA diagrams
|
|
6
|
+
class SvgRenderer
|
|
7
|
+
include Ea::Diagram::Util
|
|
8
|
+
|
|
9
|
+
attr_reader :diagram_renderer, :options, :bounds, :style_resolver
|
|
10
|
+
|
|
11
|
+
DEFAULT_OPTIONS = {
|
|
12
|
+
padding: 20,
|
|
13
|
+
background_color: "#ffffff",
|
|
14
|
+
grid_visible: false,
|
|
15
|
+
interactive: false,
|
|
16
|
+
css_classes: [],
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
def initialize(diagram_renderer, options = {})
|
|
20
|
+
@diagram_renderer = diagram_renderer
|
|
21
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
|
22
|
+
@bounds = diagram_renderer.bounds
|
|
23
|
+
@style_resolver = StyleResolver.new(options[:config_path])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Render the complete SVG diagram
|
|
27
|
+
# @return [String] Complete SVG content
|
|
28
|
+
def render # rubocop:disable Metrics/AbcSize
|
|
29
|
+
svg_content = +""
|
|
30
|
+
svg_content << svg_header
|
|
31
|
+
svg_content << defs_section
|
|
32
|
+
svg_content << background_layer
|
|
33
|
+
svg_content << grid_layer if options[:grid_visible]
|
|
34
|
+
svg_content << connectors_layer
|
|
35
|
+
svg_content << elements_layer
|
|
36
|
+
svg_content << interactive_layer if options[:interactive]
|
|
37
|
+
svg_content << svg_footer
|
|
38
|
+
svg_content
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def determine_marker_type(connector_type) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
|
|
42
|
+
normalized_type = connector_type.to_s.downcase
|
|
43
|
+
|
|
44
|
+
case normalized_type
|
|
45
|
+
when "generalization", "inheritance"
|
|
46
|
+
{ end: "url(#generalization-arrow)" }
|
|
47
|
+
when "aggregation"
|
|
48
|
+
{ start: "url(#aggregation-arrow)" }
|
|
49
|
+
when "composition"
|
|
50
|
+
{ start: "url(#composition-arrow)" }
|
|
51
|
+
when "dependency"
|
|
52
|
+
{ end: "url(#dependency-arrow)" }
|
|
53
|
+
when "realization", "implementation"
|
|
54
|
+
{ end: "url(#realization-arrow)" }
|
|
55
|
+
else
|
|
56
|
+
{ end: "url(#association-arrow)" }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def svg_header # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
63
|
+
# Bounds already include padding from LayoutEngine
|
|
64
|
+
width = bounds[:width]
|
|
65
|
+
height = bounds[:height]
|
|
66
|
+
|
|
67
|
+
# Normalize viewBox to start at 0,0 (matching EA export format)
|
|
68
|
+
# Shift all content to positive coordinates
|
|
69
|
+
offset_x = bounds[:x].negative? ? bounds[:x].abs : 0
|
|
70
|
+
offset_y = bounds[:y].negative? ? bounds[:y].abs : 0
|
|
71
|
+
total_width = width + offset_x
|
|
72
|
+
total_height = height + offset_y
|
|
73
|
+
|
|
74
|
+
view_box = "0 0 #{total_width} #{total_height}"
|
|
75
|
+
|
|
76
|
+
# Format width/height in cm (matching EA export format)
|
|
77
|
+
width_cm = format("%.2f", (total_width / 37.7952755906).round(2))
|
|
78
|
+
height_cm = format("%.2f", (total_height / 37.7952755906).round(2))
|
|
79
|
+
|
|
80
|
+
<<~SVG
|
|
81
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
82
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
|
83
|
+
|
|
84
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="#{width_cm}cm" height="#{height_cm}cm" viewBox="#{view_box}">
|
|
85
|
+
<title></title>
|
|
86
|
+
<desc>Created with Enterprise Architect (Build: 1624) 2</desc>
|
|
87
|
+
SVG
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def defs_section
|
|
91
|
+
<<~SVG
|
|
92
|
+
<defs>
|
|
93
|
+
<style type="text/css">
|
|
94
|
+
<![CDATA[
|
|
95
|
+
.lutaml-diagram-element { cursor: pointer; }
|
|
96
|
+
.lutaml-diagram-element:hover { opacity: 0.8; }
|
|
97
|
+
.lutaml-diagram-connector { fill: none; stroke: #000000; stroke-width: 1; }
|
|
98
|
+
.lutaml-diagram-connector:hover { stroke-width: 2; }
|
|
99
|
+
.lutaml-diagram-grid { stroke: #e0e0e0; stroke-width: 0.5; }
|
|
100
|
+
.lutaml-diagram-text { font-family: Arial, sans-serif; font-size: 11px; }
|
|
101
|
+
.lutaml-diagram-stereotype { font-style: italic; font-size: 9px; }
|
|
102
|
+
.lutaml-diagram-class-name { font-weight: bold; font-size: 12px; }
|
|
103
|
+
]]>
|
|
104
|
+
</style>
|
|
105
|
+
<!-- EA-style arrow markers -->
|
|
106
|
+
<marker id="generalization-arrow" markerWidth="10" markerHeight="7"
|
|
107
|
+
refX="9" refY="3.5" orient="auto">
|
|
108
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#FFFFFF" stroke="#000000" stroke-width="1" />
|
|
109
|
+
</marker>
|
|
110
|
+
<marker id="association-arrow" markerWidth="10" markerHeight="7"
|
|
111
|
+
refX="9" refY="3.5" orient="auto">
|
|
112
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#000000" />
|
|
113
|
+
</marker>
|
|
114
|
+
<marker id="aggregation-arrow" markerWidth="12" markerHeight="12"
|
|
115
|
+
refX="6" refY="6" orient="auto">
|
|
116
|
+
<polygon points="6,0 12,6 6,12 0,6" fill="#FFFFFF" stroke="#000000" stroke-width="1" />
|
|
117
|
+
</marker>
|
|
118
|
+
<marker id="composition-arrow" markerWidth="12" markerHeight="12"
|
|
119
|
+
refX="6" refY="6" orient="auto">
|
|
120
|
+
<polygon points="6,0 12,6 6,12 0,6" fill="#000000" stroke="#000000" stroke-width="1" />
|
|
121
|
+
</marker>
|
|
122
|
+
<marker id="dependency-arrow" markerWidth="10" markerHeight="7"
|
|
123
|
+
refX="9" refY="3.5" orient="auto">
|
|
124
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#000000" />
|
|
125
|
+
</marker>
|
|
126
|
+
<marker id="realization-arrow" markerWidth="10" markerHeight="7"
|
|
127
|
+
refX="9" refY="3.5" orient="auto">
|
|
128
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#FFFFFF" stroke="#000000" stroke-width="1" />
|
|
129
|
+
</marker>
|
|
130
|
+
</defs>
|
|
131
|
+
SVG
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def background_layer # rubocop:disable Metrics/AbcSize
|
|
135
|
+
offset_x = bounds[:x].negative? ? bounds[:x].abs : 0
|
|
136
|
+
offset_y = bounds[:y].negative? ? bounds[:y].abs : 0
|
|
137
|
+
total_width = bounds[:width] + offset_x
|
|
138
|
+
total_height = bounds[:height] + offset_y
|
|
139
|
+
|
|
140
|
+
<<~SVG
|
|
141
|
+
<g style="fill:#{options[:background_color]};fill-opacity:1.00;">
|
|
142
|
+
<rect x="0" y="0" width="#{total_width}" height="#{total_height}" shape-rendering="auto"/>
|
|
143
|
+
</g>
|
|
144
|
+
SVG
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def grid_layer # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
148
|
+
grid_size = 20
|
|
149
|
+
grid_lines = +""
|
|
150
|
+
|
|
151
|
+
# Vertical lines
|
|
152
|
+
x = bounds[:x]
|
|
153
|
+
while x <= bounds[:x] + bounds[:width]
|
|
154
|
+
grid_lines << "<line x1=\"#{x}\" y1=\"#{bounds[:y]}\" " \
|
|
155
|
+
"x2=\"#{x}\" " \
|
|
156
|
+
"y2=\"#{bounds[:y] + bounds[:height]}\" " \
|
|
157
|
+
"class=\"lutaml-diagram-grid\" />\n"
|
|
158
|
+
x += grid_size
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Horizontal lines
|
|
162
|
+
y = bounds[:y]
|
|
163
|
+
while y <= bounds[:y] + bounds[:height]
|
|
164
|
+
grid_lines << "<line x1=\"#{bounds[:x]}\" y1=\"#{y}\" " \
|
|
165
|
+
"x2=\"#{bounds[:x] + bounds[:width]}\" " \
|
|
166
|
+
"y2=\"#{y}\" class=\"lutaml-diagram-grid\" />\n"
|
|
167
|
+
y += grid_size
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
"<g id=\"grid-layer\" " \
|
|
171
|
+
"class=\"lutaml-diagram-grid-layer\">\n#{grid_lines}</g>\n"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def connectors_layer
|
|
175
|
+
connectors_svg = diagram_renderer.connectors.map do |connector|
|
|
176
|
+
render_connector(connector)
|
|
177
|
+
end.join("\n")
|
|
178
|
+
|
|
179
|
+
"<g id=\"connectors-layer\" " \
|
|
180
|
+
"class=\"lutaml-diagram-connectors-layer\">\n" \
|
|
181
|
+
"#{connectors_svg}\n</g>\n"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def elements_layer
|
|
185
|
+
elements_svg = diagram_renderer.elements.map do |element|
|
|
186
|
+
render_element(element)
|
|
187
|
+
end.join("\n")
|
|
188
|
+
|
|
189
|
+
"<g id=\"elements-layer\" " \
|
|
190
|
+
"class=\"lutaml-diagram-elements-layer\">\n#{elements_svg}\n</g>\n"
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def interactive_layer
|
|
194
|
+
# Add interactive JavaScript if needed
|
|
195
|
+
<<~SVG
|
|
196
|
+
<script type="text/javascript">
|
|
197
|
+
<![CDATA[
|
|
198
|
+
// Basic interactivity
|
|
199
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
200
|
+
var elements = document.querySelectorAll('.lutaml-diagram-element');
|
|
201
|
+
elements.forEach(function(el) {
|
|
202
|
+
el.addEventListener('click', function(e) {
|
|
203
|
+
var event = new CustomEvent('lutaml:element:click', {
|
|
204
|
+
detail: {
|
|
205
|
+
elementId: e.target.getAttribute('data-element-id'),
|
|
206
|
+
elementType: e.target.getAttribute('data-element-type')
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
document.dispatchEvent(event);
|
|
210
|
+
console.log('Element clicked:', e.target.getAttribute('data-element-id'));
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
]]>
|
|
215
|
+
</script>
|
|
216
|
+
SVG
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def svg_footer
|
|
220
|
+
"</svg>\n"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def render_connector(connector) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
224
|
+
path_builder = PathBuilder.new(
|
|
225
|
+
connector,
|
|
226
|
+
connector[:source_element],
|
|
227
|
+
connector[:target_element],
|
|
228
|
+
)
|
|
229
|
+
path_data = path_builder.build_path
|
|
230
|
+
|
|
231
|
+
style = style_resolver.resolve_connector_style(connector)
|
|
232
|
+
|
|
233
|
+
# Determine marker based on connector type
|
|
234
|
+
markers = determine_marker_type(connector[:type])
|
|
235
|
+
marker_start = markers[:start] || ""
|
|
236
|
+
marker_end = markers[:end] || ""
|
|
237
|
+
|
|
238
|
+
# Build style string
|
|
239
|
+
style_attrs = []
|
|
240
|
+
style_attrs << "stroke:#{style[:stroke] || '#000000'}"
|
|
241
|
+
style_attrs << "stroke-width:#{style[:stroke_width] || '1'}"
|
|
242
|
+
style_attrs << "stroke-linecap:#{style[:stroke_linecap] || 'round'}"
|
|
243
|
+
style_attrs << "stroke-linejoin:#{style[:stroke_linejoin] || 'bevel'}"
|
|
244
|
+
style_attrs << "fill:#{style[:fill] || 'none'}"
|
|
245
|
+
style_attrs << "shape-rendering:#{style[:shape_rendering] || 'auto'}"
|
|
246
|
+
if style[:stroke_dasharray]
|
|
247
|
+
style_attrs << "stroke-dasharray:#{style[:stroke_dasharray]}"
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
<<~SVG
|
|
251
|
+
<g style="#{style_attrs.join(';')}">
|
|
252
|
+
<path d="#{path_data}"
|
|
253
|
+
class="lutaml-diagram-connector lutaml-diagram-connector-#{connector[:type]}"
|
|
254
|
+
data-connector-id="#{connector[:id]}"
|
|
255
|
+
data-connector-type="#{connector[:type]}"
|
|
256
|
+
#{"marker-start=\"#{marker_start}\"" unless marker_start.empty?}
|
|
257
|
+
#{"marker-end=\"#{marker_end}\"" unless marker_end.empty?}
|
|
258
|
+
shape-rendering="auto" />
|
|
259
|
+
</g>
|
|
260
|
+
SVG
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def render_element(element)
|
|
264
|
+
registry = ElementRenderers::DEFAULT_REGISTRY
|
|
265
|
+
renderer_class = registry.renderer_for(element[:type]) ||
|
|
266
|
+
ElementRenderers::BaseRenderer
|
|
267
|
+
|
|
268
|
+
renderer = renderer_class.new(element, style_resolver)
|
|
269
|
+
renderer.render
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Diagram
|
|
5
|
+
module Util
|
|
6
|
+
# Convert a style hash to a CSS string
|
|
7
|
+
#
|
|
8
|
+
# @param style_hash [Hash] Style properties (e.g., { stroke: "#000" })
|
|
9
|
+
# @return [String] CSS string (e.g., "stroke:#000;fill:none")
|
|
10
|
+
def style_to_css(style_hash)
|
|
11
|
+
style_hash.map { |k, v| "#{k}:#{v}" }.join(";")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Parse geometry offsets
|
|
15
|
+
def parse_geometry_offsets(geometry_string)
|
|
16
|
+
geometry = parse_ea_geometry(geometry_string)
|
|
17
|
+
|
|
18
|
+
[
|
|
19
|
+
geometry[:source_offset_x].to_i,
|
|
20
|
+
geometry[:source_offset_y].to_i,
|
|
21
|
+
geometry[:target_offset_x].to_i,
|
|
22
|
+
geometry[:target_offset_y].to_i,
|
|
23
|
+
]
|
|
24
|
+
rescue TypeError, NoMethodError
|
|
25
|
+
[0, 0, 0, 0]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def parse_ea_geometry(geometry_string) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
29
|
+
return nil if geometry_string.nil? || geometry_string.strip.empty?
|
|
30
|
+
|
|
31
|
+
data = {}
|
|
32
|
+
begin
|
|
33
|
+
geometry = geometry_string
|
|
34
|
+
.gsub(/\s/, "")
|
|
35
|
+
.downcase
|
|
36
|
+
.split(";")
|
|
37
|
+
.to_h { |pair| pair.split("=") }
|
|
38
|
+
|
|
39
|
+
geometry.each do |k, v|
|
|
40
|
+
if v.include?(",")
|
|
41
|
+
# waypoints
|
|
42
|
+
data[:waypoints] ||= []
|
|
43
|
+
x_str, y_str = v.split(",")
|
|
44
|
+
data[:waypoints] << { x: x_str.to_i, y: y_str.to_i }
|
|
45
|
+
else
|
|
46
|
+
key = case k
|
|
47
|
+
when "sx"
|
|
48
|
+
data[:has_relative_coords] ||= true
|
|
49
|
+
"source_offset_x"
|
|
50
|
+
when "sy"
|
|
51
|
+
data[:has_relative_coords] ||= true
|
|
52
|
+
"source_offset_y"
|
|
53
|
+
when "ex"
|
|
54
|
+
data[:has_relative_coords] ||= true
|
|
55
|
+
"target_offset_x"
|
|
56
|
+
when "ey"
|
|
57
|
+
data[:has_relative_coords] ||= true
|
|
58
|
+
"target_offset_y"
|
|
59
|
+
else
|
|
60
|
+
k
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
data[key.to_sym] = v.to_i
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
rescue ArgumentError, TypeError
|
|
67
|
+
data = {}
|
|
68
|
+
end
|
|
69
|
+
data
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
data/lib/ea/diagram.rb
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Diagram
|
|
5
|
+
autoload :SvgRenderer, "ea/diagram/svg_renderer"
|
|
6
|
+
autoload :LayoutEngine, "ea/diagram/layout_engine"
|
|
7
|
+
autoload :StyleParser, "ea/diagram/style_parser"
|
|
8
|
+
autoload :PathBuilder, "ea/diagram/path_builder"
|
|
9
|
+
autoload :StyleResolver, "ea/diagram/style_resolver"
|
|
10
|
+
autoload :Configuration, "ea/diagram/configuration"
|
|
11
|
+
autoload :Util, "ea/diagram/util"
|
|
12
|
+
autoload :Extractor, "ea/diagram/extractor"
|
|
13
|
+
autoload :ElementRenderers, "ea/diagram/element_renderers"
|
|
14
|
+
|
|
15
|
+
class DiagramRenderer
|
|
16
|
+
attr_reader :diagram_data, :layout_engine, :style_parser
|
|
17
|
+
|
|
18
|
+
def initialize(diagram_data)
|
|
19
|
+
@diagram_data = diagram_data
|
|
20
|
+
@layout_engine = LayoutEngine.new
|
|
21
|
+
@style_parser = StyleParser.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def render_svg(options = {})
|
|
25
|
+
svg_renderer = SvgRenderer.new(self, options)
|
|
26
|
+
svg_renderer.render
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def bounds
|
|
30
|
+
layout_engine.calculate_bounds(diagram_data)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def elements
|
|
34
|
+
diagram_data[:elements] || []
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def connectors
|
|
38
|
+
diagram_data[:connectors] || []
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.render(diagram_data, options = {})
|
|
43
|
+
renderer = DiagramRenderer.new(diagram_data)
|
|
44
|
+
renderer.render_svg(options)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "benchmark"
|
|
4
|
+
|
|
5
|
+
module Ea
|
|
6
|
+
module Qea
|
|
7
|
+
# Performance benchmarking utilities for comparing QEA vs XMI parsing
|
|
8
|
+
class Benchmark
|
|
9
|
+
class << self
|
|
10
|
+
# Compare QEA and XMI parsing performance
|
|
11
|
+
#
|
|
12
|
+
# @param qea_path [String] Path to QEA file
|
|
13
|
+
# @param xmi_path [String] Path to XMI file
|
|
14
|
+
# @return [Hash] Benchmark results
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# results = Ea::Qea::Benchmark.compare(
|
|
18
|
+
# "model.qea",
|
|
19
|
+
# "model.xmi"
|
|
20
|
+
# )
|
|
21
|
+
# puts "QEA: #{results[:qea][:time]}s"
|
|
22
|
+
# puts "XMI: #{results[:xmi][:time]}s"
|
|
23
|
+
# puts "Speedup: #{results[:speedup]}x"
|
|
24
|
+
def compare(qea_path, xmi_path) # rubocop:disable Metrics/MethodLength
|
|
25
|
+
qea_result = measure_qea(qea_path)
|
|
26
|
+
xmi_result = measure_xmi(xmi_path)
|
|
27
|
+
|
|
28
|
+
speedup = if qea_result[:time].positive?
|
|
29
|
+
(xmi_result[:time] / qea_result[:time]).round(2)
|
|
30
|
+
else
|
|
31
|
+
0
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
qea: qea_result,
|
|
36
|
+
xmi: xmi_result,
|
|
37
|
+
speedup: speedup,
|
|
38
|
+
improvement_percent: ((speedup - 1) * 100).round(1),
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Measure QEA parsing performance
|
|
43
|
+
#
|
|
44
|
+
# @param path [String] Path to QEA file
|
|
45
|
+
# @return [Hash] Performance metrics
|
|
46
|
+
#
|
|
47
|
+
# @example
|
|
48
|
+
# result = Ea::Qea::Benchmark.measure_qea("model.qea")
|
|
49
|
+
# puts "Time: #{result[:time]}s"
|
|
50
|
+
# puts "Packages: #{result[:stats][:packages]}"
|
|
51
|
+
def measure_qea(path) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
52
|
+
unless File.exist?(path)
|
|
53
|
+
return { error: "File not found: #{path}" }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
result = {
|
|
57
|
+
file: path,
|
|
58
|
+
file_size_mb: (File.size(path) / 1024.0 / 1024.0).round(2),
|
|
59
|
+
format: "QEA",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Measure parsing time
|
|
63
|
+
document = nil
|
|
64
|
+
time = ::Benchmark.realtime do
|
|
65
|
+
document = Ea::Qea.parse(path)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
result[:time] = time.round(3)
|
|
69
|
+
result[:stats] = {
|
|
70
|
+
packages: document.packages&.size || 0,
|
|
71
|
+
classes: document.classes&.size || 0,
|
|
72
|
+
associations: document.associations&.size || 0,
|
|
73
|
+
diagrams: document.diagrams&.size || 0,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# Calculate throughput
|
|
77
|
+
if result[:file_size_mb].positive? && time.positive?
|
|
78
|
+
result[:throughput_mb_per_sec] =
|
|
79
|
+
(result[:file_size_mb] / time).round(2)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
result
|
|
83
|
+
rescue StandardError => e
|
|
84
|
+
{
|
|
85
|
+
error: e.message,
|
|
86
|
+
file: path,
|
|
87
|
+
format: "QEA",
|
|
88
|
+
}
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Measure XMI parsing performance
|
|
92
|
+
#
|
|
93
|
+
# @param path [String] Path to XMI file
|
|
94
|
+
# @return [Hash] Performance metrics
|
|
95
|
+
#
|
|
96
|
+
# @example
|
|
97
|
+
# result = Ea::Qea::Benchmark.measure_xmi("model.xmi")
|
|
98
|
+
# puts "Time: #{result[:time]}s"
|
|
99
|
+
def measure_xmi(path) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
100
|
+
unless File.exist?(path)
|
|
101
|
+
return { error: "File not found: #{path}" }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
result = {
|
|
105
|
+
file: path,
|
|
106
|
+
file_size_mb: (File.size(path) / 1024.0 / 1024.0).round(2),
|
|
107
|
+
format: "XMI",
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# Measure parsing time
|
|
111
|
+
document = nil
|
|
112
|
+
time = ::Benchmark.realtime do
|
|
113
|
+
File.open(path) do |file|
|
|
114
|
+
document = Ea::Xmi::Parser.parse(file)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
result[:time] = time.round(3)
|
|
119
|
+
result[:stats] = {
|
|
120
|
+
packages: document.packages&.size || 0,
|
|
121
|
+
classes: document.classes&.size || 0,
|
|
122
|
+
associations: document.associations&.size || 0,
|
|
123
|
+
diagrams: document.diagrams&.size || 0,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# Calculate throughput
|
|
127
|
+
if result[:file_size_mb].positive? && time.positive?
|
|
128
|
+
result[:throughput_mb_per_sec] =
|
|
129
|
+
(result[:file_size_mb] / time).round(2)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
result
|
|
133
|
+
rescue StandardError => e
|
|
134
|
+
{
|
|
135
|
+
error: e.message,
|
|
136
|
+
file: path,
|
|
137
|
+
format: "XMI",
|
|
138
|
+
}
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Format benchmark results for display
|
|
142
|
+
#
|
|
143
|
+
# @param results [Hash] Results from compare method
|
|
144
|
+
# @return [String] Formatted text
|
|
145
|
+
def format_results(results) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
146
|
+
return results[:error] if results[:error]
|
|
147
|
+
|
|
148
|
+
output = []
|
|
149
|
+
output << ("=" * 80)
|
|
150
|
+
output << "QEA vs XMI Performance Comparison"
|
|
151
|
+
output << ("=" * 80)
|
|
152
|
+
output << ""
|
|
153
|
+
|
|
154
|
+
if results[:qea][:error]
|
|
155
|
+
output << "QEA Error: #{results[:qea][:error]}"
|
|
156
|
+
else
|
|
157
|
+
output << "QEA File:"
|
|
158
|
+
output << " Path: #{results[:qea][:file]}"
|
|
159
|
+
output << " Size: #{results[:qea][:file_size_mb]} MB"
|
|
160
|
+
output << " Parse Time: #{results[:qea][:time]}s"
|
|
161
|
+
if results[:qea][:throughput_mb_per_sec]
|
|
162
|
+
output << " Throughput: " \
|
|
163
|
+
"#{results[:qea][:throughput_mb_per_sec]} MB/s"
|
|
164
|
+
end
|
|
165
|
+
output << " Packages: #{results[:qea][:stats][:packages]}"
|
|
166
|
+
output << " Classes: #{results[:qea][:stats][:classes]}"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
output << ""
|
|
170
|
+
|
|
171
|
+
if results[:xmi][:error]
|
|
172
|
+
output << "XMI Error: #{results[:xmi][:error]}"
|
|
173
|
+
else
|
|
174
|
+
output << "XMI File:"
|
|
175
|
+
output << " Path: #{results[:xmi][:file]}"
|
|
176
|
+
output << " Size: #{results[:xmi][:file_size_mb]} MB"
|
|
177
|
+
output << " Parse Time: #{results[:xmi][:time]}s"
|
|
178
|
+
if results[:xmi][:throughput_mb_per_sec]
|
|
179
|
+
output << " Throughput: " \
|
|
180
|
+
"#{results[:xmi][:throughput_mb_per_sec]} MB/s"
|
|
181
|
+
end
|
|
182
|
+
output << " Packages: #{results[:xmi][:stats][:packages]}"
|
|
183
|
+
output << " Classes: #{results[:xmi][:stats][:classes]}"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
output << ""
|
|
187
|
+
output << "Performance Improvement:"
|
|
188
|
+
output << " QEA is #{results[:speedup]}x faster than XMI"
|
|
189
|
+
output << " Improvement: #{results[:improvement_percent]}%"
|
|
190
|
+
output << ""
|
|
191
|
+
|
|
192
|
+
# Add interpretation
|
|
193
|
+
output << if results[:speedup] >= 10
|
|
194
|
+
" ✓ Significant performance improvement with QEA"
|
|
195
|
+
elsif results[:speedup] >= 5
|
|
196
|
+
" ✓ Notable performance improvement with QEA"
|
|
197
|
+
elsif results[:speedup] >= 2
|
|
198
|
+
" ✓ Moderate performance improvement with QEA"
|
|
199
|
+
else
|
|
200
|
+
" ~ Minimal performance difference"
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
output << ("=" * 80)
|
|
204
|
+
|
|
205
|
+
output.join("\n")
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|