ea 0.2.5 → 0.3.1
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/TODO.refactor/01-ea-model-domain-layer.md +64 -0
- data/TODO.refactor/02-ea-model-qea-source-adapter.md +58 -0
- data/TODO.refactor/03-ea-model-xmi-source-adapter.md +33 -0
- data/TODO.refactor/04-ea-model-annotations-tagged-values.md +34 -0
- data/TODO.refactor/05-ea-model-diagrams-coordinates.md +37 -0
- data/TODO.refactor/06-spa-projection-layer.md +45 -0
- data/TODO.refactor/07-spa-sharded-output-strategy.md +28 -0
- data/TODO.refactor/08-spa-build-time-search-index.md +24 -0
- data/TODO.refactor/09-spa-frontend-data-contract.md +25 -0
- data/TODO.refactor/10-spa-cli-command-rework.md +34 -0
- data/TODO.refactor/11-spa-perf-benchmark.md +23 -0
- data/TODO.refactor/12-lutaml-uml-static-site-deprecation.md +28 -0
- data/lib/ea/cli/app.rb +13 -7
- data/lib/ea/cli/command/spa.rb +33 -48
- data/lib/ea/cli/command/svg.rb +68 -0
- data/lib/ea/cli/command.rb +1 -0
- data/lib/ea/model/annotation.rb +29 -0
- data/lib/ea/model/association.rb +40 -0
- data/lib/ea/model/base.rb +27 -0
- data/lib/ea/model/bounds.rb +22 -0
- data/lib/ea/model/classifier.rb +41 -0
- data/lib/ea/model/data_type.rb +9 -0
- data/lib/ea/model/dependency.rb +19 -0
- data/lib/ea/model/diagram.rb +28 -0
- data/lib/ea/model/diagram_connector.rb +30 -0
- data/lib/ea/model/diagram_element.rb +23 -0
- data/lib/ea/model/document.rb +107 -0
- data/lib/ea/model/enumeration.rb +15 -0
- data/lib/ea/model/enumeration_literal.rb +16 -0
- data/lib/ea/model/generalization.rb +19 -0
- data/lib/ea/model/interface.rb +9 -0
- data/lib/ea/model/klass.rb +16 -0
- data/lib/ea/model/metadata.rb +31 -0
- data/lib/ea/model/operation.rb +33 -0
- data/lib/ea/model/package.rb +36 -0
- data/lib/ea/model/parameter.rb +24 -0
- data/lib/ea/model/point.rb +16 -0
- data/lib/ea/model/polymorphic_maps.rb +30 -0
- data/lib/ea/model/primitive_type.rb +9 -0
- data/lib/ea/model/property.rb +49 -0
- data/lib/ea/model/realization.rb +19 -0
- data/lib/ea/model/relationship.rb +21 -0
- data/lib/ea/model/stereotype.rb +24 -0
- data/lib/ea/model/tagged_value.rb +20 -0
- data/lib/ea/model/waypoint.rb +19 -0
- data/lib/ea/model.rb +48 -0
- data/lib/ea/sources/qea/adapter.rb +63 -0
- data/lib/ea/sources/qea/annotation_builder.rb +36 -0
- data/lib/ea/sources/qea/classifier_builder.rb +119 -0
- data/lib/ea/sources/qea/connector_relationship_map.rb +42 -0
- data/lib/ea/sources/qea/diagram_builder.rb +214 -0
- data/lib/ea/sources/qea/diagram_style_parser.rb +43 -0
- data/lib/ea/sources/qea/id_normalizer.rb +24 -0
- data/lib/ea/sources/qea/metadata_builder.rb +42 -0
- data/lib/ea/sources/qea/object_classifier_map.rb +36 -0
- data/lib/ea/sources/qea/operation_builder.rb +79 -0
- data/lib/ea/sources/qea/package_builder.rb +44 -0
- data/lib/ea/sources/qea/property_builder.rb +86 -0
- data/lib/ea/sources/qea/relationship_builder.rb +113 -0
- data/lib/ea/sources/qea/stereotype_builder.rb +44 -0
- data/lib/ea/sources/qea/tagged_value_builder.rb +39 -0
- data/lib/ea/sources/qea.rb +31 -0
- data/lib/ea/sources/xmi/adapter.rb +62 -0
- data/lib/ea/sources/xmi/annotation_builder.rb +49 -0
- data/lib/ea/sources/xmi/classifier_builder.rb +89 -0
- data/lib/ea/sources/xmi/diagram_builder.rb +82 -0
- data/lib/ea/sources/xmi/id_normalizer.rb +27 -0
- data/lib/ea/sources/xmi/metadata_builder.rb +32 -0
- data/lib/ea/sources/xmi/operation_builder.rb +59 -0
- data/lib/ea/sources/xmi/package_builder.rb +73 -0
- data/lib/ea/sources/xmi/property_builder.rb +77 -0
- data/lib/ea/sources/xmi/relationship_builder.rb +142 -0
- data/lib/ea/sources/xmi/relationship_type_map.rb +34 -0
- data/lib/ea/sources/xmi/type_classifier_map.rb +33 -0
- data/lib/ea/sources/xmi.rb +29 -0
- data/lib/ea/sources.rb +15 -0
- data/lib/ea/spa/configuration.rb +110 -0
- data/lib/ea/spa/generator.rb +45 -0
- data/lib/ea/spa/lazy_ref.rb +18 -0
- data/lib/ea/spa/output/sharded_multi_file_strategy.rb +71 -0
- data/lib/ea/spa/output/single_file_strategy.rb +56 -0
- data/lib/ea/spa/output/strategy.rb +36 -0
- data/lib/ea/spa/output.rb +11 -0
- data/lib/ea/spa/package_tree.rb +15 -0
- data/lib/ea/spa/package_tree_node.rb +26 -0
- data/lib/ea/spa/projector.rb +199 -0
- data/lib/ea/spa/search_entry.rb +28 -0
- data/lib/ea/spa/search_index.rb +18 -0
- data/lib/ea/spa/shard.rb +19 -0
- data/lib/ea/spa/skeleton.rb +22 -0
- data/lib/ea/spa/skeleton_entry.rb +27 -0
- data/lib/ea/spa.rb +31 -0
- data/lib/ea/svg/bounds_calculator.rb +74 -0
- data/lib/ea/svg/connector_path.rb +138 -0
- data/lib/ea/svg/element_box.rb +256 -0
- data/lib/ea/svg/renderer.rb +77 -0
- data/lib/ea/svg/style_resolver.rb +72 -0
- data/lib/ea/svg.rb +21 -0
- data/lib/ea/version.rb +1 -1
- data/lib/ea.rb +4 -0
- metadata +97 -2
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ostruct"
|
|
4
|
+
|
|
5
|
+
module Ea
|
|
6
|
+
module Svg
|
|
7
|
+
# Renders one DiagramElement as a 3-compartment UML class box,
|
|
8
|
+
# matching EA's drawing convention:
|
|
9
|
+
#
|
|
10
|
+
# ┌─────────────────────────┐
|
|
11
|
+
# │ «Stereotype» │ ← header (stereotype + name)
|
|
12
|
+
# │ qualified::Name │
|
|
13
|
+
# ├─────────────────────────┤
|
|
14
|
+
# │ + attr1: Type [0..1] │ ← attributes
|
|
15
|
+
# │ + attr2: Type [0..*] │
|
|
16
|
+
# ├─────────────────────────┤
|
|
17
|
+
# │ + op1(arg: T): T │ ← operations
|
|
18
|
+
# └─────────────────────────┘
|
|
19
|
+
#
|
|
20
|
+
# The stored element bounds are honored as the outer rectangle;
|
|
21
|
+
# internal layout (header / attrs / ops heights) is computed
|
|
22
|
+
# proportionally based on line count.
|
|
23
|
+
class ElementBox
|
|
24
|
+
HEADER_LINE_HEIGHT = 17
|
|
25
|
+
ATTR_LINE_HEIGHT = 17
|
|
26
|
+
OP_LINE_HEIGHT = 17
|
|
27
|
+
TOP_PADDING = 17
|
|
28
|
+
|
|
29
|
+
STEREOTYPE_COLORS = {
|
|
30
|
+
"featuretype" => "#FFFFCC", # yellow
|
|
31
|
+
"feature" => "#FFFFCC",
|
|
32
|
+
"type" => "#CCFFCC", # green
|
|
33
|
+
"datatype" => "#FFCCFF", # pink
|
|
34
|
+
"basictype" => "#FFCCFF",
|
|
35
|
+
"codelist" => "#FFCCFF",
|
|
36
|
+
"enumeration" => "#FFCCFF",
|
|
37
|
+
"union" => "#F0E68C", # khaki
|
|
38
|
+
"adeelement" => "#F5F5DC" # beige
|
|
39
|
+
}.freeze
|
|
40
|
+
DEFAULT_FILL = "#FFFFFF"
|
|
41
|
+
|
|
42
|
+
attr_reader :element, :model_index
|
|
43
|
+
|
|
44
|
+
def initialize(element, model_index:)
|
|
45
|
+
@element = element
|
|
46
|
+
@model_index = model_index
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def render
|
|
50
|
+
return "" unless element.bounds
|
|
51
|
+
|
|
52
|
+
b = normalize_bounds(element.bounds)
|
|
53
|
+
classifier = bound_classifier
|
|
54
|
+
return render_simple_box(b) unless classifier
|
|
55
|
+
|
|
56
|
+
render_class_box(b, classifier)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def bound_classifier
|
|
62
|
+
ref = element.model_element_ref
|
|
63
|
+
return nil unless ref
|
|
64
|
+
|
|
65
|
+
candidate = model_index[ref]
|
|
66
|
+
return nil unless candidate.is_a?(Ea::Model::Classifier)
|
|
67
|
+
|
|
68
|
+
candidate
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def render_simple_box(bounds)
|
|
72
|
+
style = StyleResolver.new(element.style)
|
|
73
|
+
<<~SVG.chomp
|
|
74
|
+
<g class="element" data-element-id="#{escape(element.id)}">
|
|
75
|
+
<rect x="#{bounds.x}" y="#{bounds.y}" width="#{bounds.width}" height="#{bounds.height}"
|
|
76
|
+
fill="#{fill_color(style)}" stroke="#{style.stroke_color}"
|
|
77
|
+
stroke-width="#{style.stroke_width}"/>
|
|
78
|
+
<text x="#{bounds.x + (bounds.width / 2.0)}"
|
|
79
|
+
y="#{bounds.y + (bounds.height / 2.0)}"
|
|
80
|
+
text-anchor="middle" dominant-baseline="middle"
|
|
81
|
+
fill="#{style.font_color}"
|
|
82
|
+
font-family="sans-serif" font-size="14">#{escape(element.id)}</text>
|
|
83
|
+
</g>
|
|
84
|
+
SVG
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def render_class_box(bounds, classifier)
|
|
88
|
+
stereotype = primary_stereotype(classifier)
|
|
89
|
+
fill = STEREOTYPE_COLORS[stereotype&.downcase] || DEFAULT_FILL
|
|
90
|
+
style = StyleResolver.new(element.style)
|
|
91
|
+
stroke = style.stroke_color
|
|
92
|
+
stroke_width = style.stroke_width
|
|
93
|
+
font_color = style.font_color
|
|
94
|
+
|
|
95
|
+
header_lines = build_header_lines(classifier, stereotype)
|
|
96
|
+
attr_lines = build_attribute_lines(classifier)
|
|
97
|
+
op_lines = build_operation_lines(classifier)
|
|
98
|
+
|
|
99
|
+
header_bottom = bounds.y + header_height(header_lines.size)
|
|
100
|
+
attrs_bottom = header_bottom + compartment_height(attr_lines.size)
|
|
101
|
+
ops_bottom = attrs_bottom + compartment_height(op_lines.size)
|
|
102
|
+
|
|
103
|
+
parts = []
|
|
104
|
+
parts << %(<g class="element" data-element-id="#{escape(element.id)}">)
|
|
105
|
+
parts << render_outer_rect(bounds, fill, stroke, stroke_width)
|
|
106
|
+
parts << render_header_divider(bounds, header_bottom, stroke, stroke_width)
|
|
107
|
+
parts << render_attrs_divider(bounds, attrs_bottom, stroke, stroke_width) if op_lines.any?
|
|
108
|
+
parts << render_header_text(bounds, header_lines, font_color)
|
|
109
|
+
parts << render_attribute_text(bounds, header_bottom, attr_lines, font_color)
|
|
110
|
+
parts << render_operation_text(bounds, attrs_bottom, op_lines, font_color)
|
|
111
|
+
parts << %(</g>)
|
|
112
|
+
parts.join("\n ")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def build_header_lines(classifier, stereotype)
|
|
116
|
+
lines = []
|
|
117
|
+
lines << "«#{stereotype}»" if stereotype && !stereotype.empty?
|
|
118
|
+
lines << (classifier.qualified_name&.split("::")&.last || classifier.name || "(unnamed)")
|
|
119
|
+
lines
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def build_attribute_lines(classifier)
|
|
123
|
+
return [] unless classifier.properties
|
|
124
|
+
|
|
125
|
+
classifier.properties.map do |prop|
|
|
126
|
+
vis = visibility_marker(prop.visibility)
|
|
127
|
+
mult = multiplicity_text(prop)
|
|
128
|
+
type = prop.type_name ? ": #{prop.type_name}" : ""
|
|
129
|
+
"#{vis} #{prop.name}#{type}#{mult}"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def build_operation_lines(classifier)
|
|
134
|
+
return [] unless classifier.operations
|
|
135
|
+
|
|
136
|
+
classifier.operations.map do |op|
|
|
137
|
+
vis = visibility_marker(op.visibility)
|
|
138
|
+
ret = op.return_type_name ? ": #{op.return_type_name}" : ""
|
|
139
|
+
params = (op.parameters || []).map { |p| "#{p.name}: #{p.type_name || 'T'}" }.join(", ")
|
|
140
|
+
"#{vis} #{op.name}(#{params})#{ret}"
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def visibility_marker(visibility)
|
|
145
|
+
case visibility&.downcase
|
|
146
|
+
when "public" then "+"
|
|
147
|
+
when "private" then "-"
|
|
148
|
+
when "protected" then "#"
|
|
149
|
+
when "package" then "~"
|
|
150
|
+
else "+"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def multiplicity_text(prop)
|
|
155
|
+
lower = prop.multiplicity_lower
|
|
156
|
+
upper = prop.multiplicity_upper
|
|
157
|
+
return "" if lower.nil? && upper.nil?
|
|
158
|
+
return "" if lower == 1 && upper == 1
|
|
159
|
+
|
|
160
|
+
upper_text = upper == -1 ? "*" : upper
|
|
161
|
+
lower_text = lower || 0
|
|
162
|
+
return "[0..*]" if lower_text == 0 && upper == -1
|
|
163
|
+
return "[#{lower_text}]" if lower_text == upper
|
|
164
|
+
|
|
165
|
+
"[#{lower_text}..#{upper_text}]"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def primary_stereotype(classifier)
|
|
169
|
+
refs = classifier.stereotype_refs
|
|
170
|
+
return nil if refs.nil? || refs.empty?
|
|
171
|
+
|
|
172
|
+
refs.first.to_s.split("::").last
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def fill_color(style)
|
|
176
|
+
stereotype = primary_stereotype_from_style
|
|
177
|
+
STEREOTYPE_COLORS[stereotype&.downcase] || style.fill_color
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def primary_stereotype_from_style
|
|
181
|
+
nil
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def header_height(line_count)
|
|
185
|
+
TOP_PADDING + (line_count * HEADER_LINE_HEIGHT)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def compartment_height(line_count)
|
|
189
|
+
return 0 if line_count.zero?
|
|
190
|
+
|
|
191
|
+
10 + (line_count * (line_count == 0 ? 0 : (line_count < 3 ? 17 : 17)))
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def render_outer_rect(bounds, fill, stroke, stroke_width)
|
|
195
|
+
%( <rect x="#{bounds.x}" y="#{bounds.y}" width="#{bounds.width}" height="#{bounds.height}" fill="#{fill}" stroke="#{stroke}" stroke-width="#{stroke_width}"/>)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def render_header_divider(bounds, y, stroke, stroke_width)
|
|
199
|
+
%( <path d="M #{bounds.x} #{y} L #{bounds.x + bounds.width} #{y}" stroke="#{stroke}" stroke-width="#{stroke_width}" fill="none"/>)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def render_attrs_divider(bounds, y, stroke, stroke_width)
|
|
203
|
+
%( <path d="M #{bounds.x} #{y} L #{bounds.x + bounds.width} #{y}" stroke="#{stroke}" stroke-width="#{stroke_width}" fill="none"/>)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def render_header_text(bounds, lines, font_color)
|
|
207
|
+
center_x = bounds.x + (bounds.width / 2.0)
|
|
208
|
+
lines.each_with_index.map do |line, idx|
|
|
209
|
+
y = bounds.y + TOP_PADDING + (idx * HEADER_LINE_HEIGHT) - 4
|
|
210
|
+
weight = (idx == lines.size - 1) ? "bold" : "normal"
|
|
211
|
+
%( <text x="#{center_x}" y="#{y}" text-anchor="middle" fill="#{font_color}" font-family="sans-serif" font-size="13" font-weight="#{weight}">#{escape(line)}</text>)
|
|
212
|
+
end.join("\n ")
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def render_attribute_text(bounds, header_bottom, lines, font_color)
|
|
216
|
+
return "" if lines.empty?
|
|
217
|
+
|
|
218
|
+
left = bounds.x + 5
|
|
219
|
+
lines.each_with_index.map do |line, idx|
|
|
220
|
+
y = header_bottom + 14 + (idx * ATTR_LINE_HEIGHT)
|
|
221
|
+
%( <text x="#{left}" y="#{y}" text-anchor="start" fill="#{font_color}" font-family="sans-serif" font-size="12">#{escape(line)}</text>)
|
|
222
|
+
end.join("\n ")
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def render_operation_text(bounds, attrs_bottom, lines, font_color)
|
|
226
|
+
return "" if lines.empty?
|
|
227
|
+
|
|
228
|
+
left = bounds.x + 5
|
|
229
|
+
lines.each_with_index.map do |line, idx|
|
|
230
|
+
y = attrs_bottom + 14 + (idx * OP_LINE_HEIGHT)
|
|
231
|
+
%( <text x="#{left}" y="#{y}" text-anchor="start" fill="#{font_color}" font-family="sans-serif" font-size="12">#{escape(line)}</text>)
|
|
232
|
+
end.join("\n ")
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def normalize_bounds(bounds)
|
|
236
|
+
x = bounds.x
|
|
237
|
+
y = bounds.y
|
|
238
|
+
w = bounds.width
|
|
239
|
+
h = bounds.height
|
|
240
|
+
x, w = x + w, -w if w.negative?
|
|
241
|
+
y, h = y + h, -h if h.negative?
|
|
242
|
+
OpenStruct.new(x: x, y: y, width: w, height: h)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def escape(text)
|
|
246
|
+
return "" if text.nil?
|
|
247
|
+
|
|
248
|
+
text.to_s
|
|
249
|
+
.gsub("&", "&")
|
|
250
|
+
.gsub("<", "<")
|
|
251
|
+
.gsub(">", ">")
|
|
252
|
+
.gsub("\"", """)
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ostruct"
|
|
4
|
+
|
|
5
|
+
module Ea
|
|
6
|
+
module Svg
|
|
7
|
+
# Renders an Ea::Model::Diagram into a standalone SVG string.
|
|
8
|
+
# Coordinates come straight from the umldi (UML Diagram
|
|
9
|
+
# Interchange) content captured by the source adapter:
|
|
10
|
+
# DiagramElement bounds and DiagramConnector waypoints.
|
|
11
|
+
#
|
|
12
|
+
# The renderer walks the diagram once, computes the union
|
|
13
|
+
# bounding box, and emits SVG in the same coordinate space
|
|
14
|
+
# (no y-flip; SVG's +y is down, same as EA's pixel space).
|
|
15
|
+
class Renderer
|
|
16
|
+
DEFAULT_PADDING = 20
|
|
17
|
+
|
|
18
|
+
attr_reader :diagram, :model_index, :options
|
|
19
|
+
|
|
20
|
+
def initialize(diagram, model_index: {}, padding: DEFAULT_PADDING)
|
|
21
|
+
@diagram = diagram
|
|
22
|
+
@model_index = model_index
|
|
23
|
+
@options = { padding: padding }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def render
|
|
27
|
+
bounds = BoundsCalculator.new(diagram).compute
|
|
28
|
+
padded = pad(bounds)
|
|
29
|
+
|
|
30
|
+
<<~SVG
|
|
31
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
32
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
33
|
+
viewBox="#{padded.x} #{padded.y} #{padded.width} #{padded.height}"
|
|
34
|
+
width="#{padded.width}" height="#{padded.height}"
|
|
35
|
+
font-family="sans-serif">
|
|
36
|
+
<rect x="#{padded.x}" y="#{padded.y}"
|
|
37
|
+
width="#{padded.width}" height="#{padded.height}"
|
|
38
|
+
fill="white"/>
|
|
39
|
+
#{render_elements}
|
|
40
|
+
#{render_connectors}
|
|
41
|
+
</svg>
|
|
42
|
+
SVG
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def render_elements
|
|
48
|
+
diagram.elements.map do |elem|
|
|
49
|
+
ElementBox.new(elem, model_index: model_index).render
|
|
50
|
+
end.join("\n ")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def render_connectors
|
|
54
|
+
diagram.connectors.map do |conn|
|
|
55
|
+
ConnectorPath.new(conn,
|
|
56
|
+
relationship: relationship_for(conn)).render
|
|
57
|
+
end.join("\n ")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def relationship_for(connector)
|
|
61
|
+
return nil unless connector.relationship_ref
|
|
62
|
+
|
|
63
|
+
model_index[connector.relationship_ref]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def pad(bounds)
|
|
67
|
+
p = options[:padding]
|
|
68
|
+
OpenStruct.new(
|
|
69
|
+
x: bounds.x - p,
|
|
70
|
+
y: bounds.y - p,
|
|
71
|
+
width: bounds.width + (2 * p),
|
|
72
|
+
height: bounds.height + (2 * p)
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Svg
|
|
5
|
+
# Translates the parsed style hash (from
|
|
6
|
+
# Ea::Sources::Qea::DiagramStyleParser, or the equivalent for
|
|
7
|
+
# XMI) into SVG-friendly attributes. EA packs colors as
|
|
8
|
+
# integers (e.g. 16777215 = white); we translate to CSS hex.
|
|
9
|
+
class StyleResolver
|
|
10
|
+
DEFAULT_FILL = "#ffffff"
|
|
11
|
+
DEFAULT_STROKE = "#000000"
|
|
12
|
+
DEFAULT_FONT_COLOR = "#000000"
|
|
13
|
+
|
|
14
|
+
attr_reader :style
|
|
15
|
+
|
|
16
|
+
def initialize(style_hash)
|
|
17
|
+
@style = style_hash || {}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def fill_color
|
|
21
|
+
color_from_ea(style[:bcol]) || DEFAULT_FILL
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def stroke_color
|
|
25
|
+
color_from_ea(style[:lcol]) || DEFAULT_STROKE
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def font_color
|
|
29
|
+
color_from_ea(style[:fcol]) || DEFAULT_FONT_COLOR
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def stroke_width
|
|
33
|
+
raw = style[:lwd]
|
|
34
|
+
return 1 if raw.nil? || raw.to_s.empty?
|
|
35
|
+
|
|
36
|
+
Integer(raw)
|
|
37
|
+
rescue ArgumentError
|
|
38
|
+
1
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_svg_attrs
|
|
42
|
+
{
|
|
43
|
+
fill: fill_color,
|
|
44
|
+
stroke: stroke_color,
|
|
45
|
+
"stroke-width": stroke_width
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# EA stores colors as decimal integers in BGR byte order
|
|
50
|
+
# (low byte = red, high byte = blue). The high byte may hold
|
|
51
|
+
# alpha in some variants; we treat anything above 0xFFFFFF
|
|
52
|
+
# as opaque.
|
|
53
|
+
def color_from_ea(raw)
|
|
54
|
+
return nil if raw.nil? || raw.to_s.empty?
|
|
55
|
+
|
|
56
|
+
# Hex strings (e.g. from XMI): passthrough.
|
|
57
|
+
return "##{raw}" if raw.to_s.match?(/\A[0-9a-fA-F]{6}\z/)
|
|
58
|
+
|
|
59
|
+
Integer(raw.to_s)
|
|
60
|
+
rescue ArgumentError
|
|
61
|
+
nil
|
|
62
|
+
else
|
|
63
|
+
# Convert BGR integer → RGB hex.
|
|
64
|
+
value = Integer(raw)
|
|
65
|
+
b = (value >> 16) & 0xff
|
|
66
|
+
g = (value >> 8) & 0xff
|
|
67
|
+
r = value & 0xff
|
|
68
|
+
format("#%02X%02X%02X", r, g, b)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/ea/svg.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
# Ea::Svg is a consumer adapter of Ea::Model::Diagram — it
|
|
5
|
+
# projects the umldi (UML Diagram Interchange) content of a
|
|
6
|
+
# diagram into standalone SVG. Coordinates are taken straight
|
|
7
|
+
# from the source: EA's pixel space (rectleft, recttop,
|
|
8
|
+
# rectright, rectbottom, t_diagramlinks.geometry) or XMI's
|
|
9
|
+
# uml:Diagram owned_element bounds.
|
|
10
|
+
#
|
|
11
|
+
# Distinct from Ea::Diagram::SvgRenderer which operates on the
|
|
12
|
+
# legacy Lutaml::Uml::Document pipeline. This module consumes
|
|
13
|
+
# the canonical Ea::Model types only.
|
|
14
|
+
module Svg
|
|
15
|
+
autoload :BoundsCalculator, "ea/svg/bounds_calculator"
|
|
16
|
+
autoload :StyleResolver, "ea/svg/style_resolver"
|
|
17
|
+
autoload :ElementBox, "ea/svg/element_box"
|
|
18
|
+
autoload :ConnectorPath, "ea/svg/connector_path"
|
|
19
|
+
autoload :Renderer, "ea/svg/renderer"
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/ea/version.rb
CHANGED
data/lib/ea.rb
CHANGED
|
@@ -9,6 +9,10 @@ module Ea
|
|
|
9
9
|
class Error < StandardError; end
|
|
10
10
|
|
|
11
11
|
autoload :Qea, "ea/qea"
|
|
12
|
+
autoload :Model, "ea/model"
|
|
13
|
+
autoload :Sources, "ea/sources"
|
|
14
|
+
autoload :Spa, "ea/spa"
|
|
15
|
+
autoload :Svg, "ea/svg"
|
|
12
16
|
autoload :Diagram, "ea/diagram"
|
|
13
17
|
autoload :Transformations, "ea/transformations"
|
|
14
18
|
autoload :Xmi, "ea/xmi"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ea
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: lutaml-model
|
|
@@ -234,6 +234,18 @@ files:
|
|
|
234
234
|
- TODO.next/38-xmi-parser-feature-parity.md
|
|
235
235
|
- TODO.next/39-ea-spa-cli-command.md
|
|
236
236
|
- TODO.next/40-drop-lur-only-check-in-diagrams-extract.md
|
|
237
|
+
- TODO.refactor/01-ea-model-domain-layer.md
|
|
238
|
+
- TODO.refactor/02-ea-model-qea-source-adapter.md
|
|
239
|
+
- TODO.refactor/03-ea-model-xmi-source-adapter.md
|
|
240
|
+
- TODO.refactor/04-ea-model-annotations-tagged-values.md
|
|
241
|
+
- TODO.refactor/05-ea-model-diagrams-coordinates.md
|
|
242
|
+
- TODO.refactor/06-spa-projection-layer.md
|
|
243
|
+
- TODO.refactor/07-spa-sharded-output-strategy.md
|
|
244
|
+
- TODO.refactor/08-spa-build-time-search-index.md
|
|
245
|
+
- TODO.refactor/09-spa-frontend-data-contract.md
|
|
246
|
+
- TODO.refactor/10-spa-cli-command-rework.md
|
|
247
|
+
- TODO.refactor/11-spa-perf-benchmark.md
|
|
248
|
+
- TODO.refactor/12-lutaml-uml-static-site-deprecation.md
|
|
237
249
|
- TODO.release/01-release-lutaml-uml-030.md
|
|
238
250
|
- TODO.release/02-fix-lutaml-meta-gem-requires.md
|
|
239
251
|
- TODO.release/03-release-lutaml-0110.md
|
|
@@ -281,6 +293,7 @@ files:
|
|
|
281
293
|
- lib/ea/cli/command/repository_builder.rb
|
|
282
294
|
- lib/ea/cli/command/spa.rb
|
|
283
295
|
- lib/ea/cli/command/stats.rb
|
|
296
|
+
- lib/ea/cli/command/svg.rb
|
|
284
297
|
- lib/ea/cli/command/validate.rb
|
|
285
298
|
- lib/ea/cli/error.rb
|
|
286
299
|
- lib/ea/cli/output.rb
|
|
@@ -302,6 +315,36 @@ files:
|
|
|
302
315
|
- lib/ea/diagram/style_resolver.rb
|
|
303
316
|
- lib/ea/diagram/svg_renderer.rb
|
|
304
317
|
- lib/ea/diagram/util.rb
|
|
318
|
+
- lib/ea/model.rb
|
|
319
|
+
- lib/ea/model/annotation.rb
|
|
320
|
+
- lib/ea/model/association.rb
|
|
321
|
+
- lib/ea/model/base.rb
|
|
322
|
+
- lib/ea/model/bounds.rb
|
|
323
|
+
- lib/ea/model/classifier.rb
|
|
324
|
+
- lib/ea/model/data_type.rb
|
|
325
|
+
- lib/ea/model/dependency.rb
|
|
326
|
+
- lib/ea/model/diagram.rb
|
|
327
|
+
- lib/ea/model/diagram_connector.rb
|
|
328
|
+
- lib/ea/model/diagram_element.rb
|
|
329
|
+
- lib/ea/model/document.rb
|
|
330
|
+
- lib/ea/model/enumeration.rb
|
|
331
|
+
- lib/ea/model/enumeration_literal.rb
|
|
332
|
+
- lib/ea/model/generalization.rb
|
|
333
|
+
- lib/ea/model/interface.rb
|
|
334
|
+
- lib/ea/model/klass.rb
|
|
335
|
+
- lib/ea/model/metadata.rb
|
|
336
|
+
- lib/ea/model/operation.rb
|
|
337
|
+
- lib/ea/model/package.rb
|
|
338
|
+
- lib/ea/model/parameter.rb
|
|
339
|
+
- lib/ea/model/point.rb
|
|
340
|
+
- lib/ea/model/polymorphic_maps.rb
|
|
341
|
+
- lib/ea/model/primitive_type.rb
|
|
342
|
+
- lib/ea/model/property.rb
|
|
343
|
+
- lib/ea/model/realization.rb
|
|
344
|
+
- lib/ea/model/relationship.rb
|
|
345
|
+
- lib/ea/model/stereotype.rb
|
|
346
|
+
- lib/ea/model/tagged_value.rb
|
|
347
|
+
- lib/ea/model/waypoint.rb
|
|
305
348
|
- lib/ea/qea.rb
|
|
306
349
|
- lib/ea/qea/benchmark.rb
|
|
307
350
|
- lib/ea/qea/database.rb
|
|
@@ -390,6 +433,58 @@ files:
|
|
|
390
433
|
- lib/ea/qea/verification/document_verifier.rb
|
|
391
434
|
- lib/ea/qea/verification/element_comparator.rb
|
|
392
435
|
- lib/ea/qea/verification/structure_matcher.rb
|
|
436
|
+
- lib/ea/sources.rb
|
|
437
|
+
- lib/ea/sources/qea.rb
|
|
438
|
+
- lib/ea/sources/qea/adapter.rb
|
|
439
|
+
- lib/ea/sources/qea/annotation_builder.rb
|
|
440
|
+
- lib/ea/sources/qea/classifier_builder.rb
|
|
441
|
+
- lib/ea/sources/qea/connector_relationship_map.rb
|
|
442
|
+
- lib/ea/sources/qea/diagram_builder.rb
|
|
443
|
+
- lib/ea/sources/qea/diagram_style_parser.rb
|
|
444
|
+
- lib/ea/sources/qea/id_normalizer.rb
|
|
445
|
+
- lib/ea/sources/qea/metadata_builder.rb
|
|
446
|
+
- lib/ea/sources/qea/object_classifier_map.rb
|
|
447
|
+
- lib/ea/sources/qea/operation_builder.rb
|
|
448
|
+
- lib/ea/sources/qea/package_builder.rb
|
|
449
|
+
- lib/ea/sources/qea/property_builder.rb
|
|
450
|
+
- lib/ea/sources/qea/relationship_builder.rb
|
|
451
|
+
- lib/ea/sources/qea/stereotype_builder.rb
|
|
452
|
+
- lib/ea/sources/qea/tagged_value_builder.rb
|
|
453
|
+
- lib/ea/sources/xmi.rb
|
|
454
|
+
- lib/ea/sources/xmi/adapter.rb
|
|
455
|
+
- lib/ea/sources/xmi/annotation_builder.rb
|
|
456
|
+
- lib/ea/sources/xmi/classifier_builder.rb
|
|
457
|
+
- lib/ea/sources/xmi/diagram_builder.rb
|
|
458
|
+
- lib/ea/sources/xmi/id_normalizer.rb
|
|
459
|
+
- lib/ea/sources/xmi/metadata_builder.rb
|
|
460
|
+
- lib/ea/sources/xmi/operation_builder.rb
|
|
461
|
+
- lib/ea/sources/xmi/package_builder.rb
|
|
462
|
+
- lib/ea/sources/xmi/property_builder.rb
|
|
463
|
+
- lib/ea/sources/xmi/relationship_builder.rb
|
|
464
|
+
- lib/ea/sources/xmi/relationship_type_map.rb
|
|
465
|
+
- lib/ea/sources/xmi/type_classifier_map.rb
|
|
466
|
+
- lib/ea/spa.rb
|
|
467
|
+
- lib/ea/spa/configuration.rb
|
|
468
|
+
- lib/ea/spa/generator.rb
|
|
469
|
+
- lib/ea/spa/lazy_ref.rb
|
|
470
|
+
- lib/ea/spa/output.rb
|
|
471
|
+
- lib/ea/spa/output/sharded_multi_file_strategy.rb
|
|
472
|
+
- lib/ea/spa/output/single_file_strategy.rb
|
|
473
|
+
- lib/ea/spa/output/strategy.rb
|
|
474
|
+
- lib/ea/spa/package_tree.rb
|
|
475
|
+
- lib/ea/spa/package_tree_node.rb
|
|
476
|
+
- lib/ea/spa/projector.rb
|
|
477
|
+
- lib/ea/spa/search_entry.rb
|
|
478
|
+
- lib/ea/spa/search_index.rb
|
|
479
|
+
- lib/ea/spa/shard.rb
|
|
480
|
+
- lib/ea/spa/skeleton.rb
|
|
481
|
+
- lib/ea/spa/skeleton_entry.rb
|
|
482
|
+
- lib/ea/svg.rb
|
|
483
|
+
- lib/ea/svg/bounds_calculator.rb
|
|
484
|
+
- lib/ea/svg/connector_path.rb
|
|
485
|
+
- lib/ea/svg/element_box.rb
|
|
486
|
+
- lib/ea/svg/renderer.rb
|
|
487
|
+
- lib/ea/svg/style_resolver.rb
|
|
393
488
|
- lib/ea/transformations.rb
|
|
394
489
|
- lib/ea/transformations/configuration.rb
|
|
395
490
|
- lib/ea/transformations/format_registry.rb
|