ea 0.5.0 → 0.5.2
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/CHANGELOG.md +128 -1
- data/TODO.complete/51-remove-respond-to.md +25 -0
- data/TODO.complete/52-xsd-decouple-fixtures.md +23 -0
- data/TODO.complete/53-xmi-public-send-cleanup.md +26 -0
- data/TODO.complete/54-wire-stereotype-icon-renderer.md +20 -0
- data/TODO.complete/55-wire-shapescript.md +20 -0
- data/TODO.complete/56-wire-emf-renderer.md +27 -0
- data/TODO.complete/57-wire-ocl-evaluator.md +23 -0
- data/TODO.complete/58-diff-modifications.md +21 -0
- data/TODO.complete/59-delete-toy-xmi-export.md +30 -0
- data/TODO.complete/60-curate-json-export.md +20 -0
- data/TODO.complete/61-plantuml-relationships.md +22 -0
- data/TODO.complete/62-changelog.md +17 -0
- data/TODO.complete/63-dependabot-fix.md +18 -0
- data/TODO.complete/64-qeatoxmi-connectors.md +30 -0
- data/TODO.complete/65-qeatoxmi-diagrams.md +29 -0
- data/TODO.complete/66-qeatoxmi-style-tags-docs.md +28 -0
- data/TODO.complete/67-ocl-collection-ops.md +20 -0
- data/TODO.complete/68-ocl-comparison-ops.md +19 -0
- data/TODO.complete/69-spec-new-models.md +25 -0
- data/TODO.complete/STATUS.md +40 -64
- data/lib/ea/cli/command/convert.rb +1 -1
- data/lib/ea/cli/command/export.rb +23 -2
- data/lib/ea/diff/comparator.rb +28 -1
- data/lib/ea/export/json/generator.rb +84 -8
- data/lib/ea/export/plantuml/generator.rb +95 -28
- data/lib/ea/export/xsd/generator.rb +10 -15
- data/lib/ea/export.rb +0 -1
- data/lib/ea/lint/rules/missing_stereotype.rb +16 -11
- data/lib/ea/ocl/evaluator.rb +39 -2
- data/lib/ea/ocl/nodes.rb +9 -0
- data/lib/ea/ocl/parser.rb +60 -6
- data/lib/ea/qea/models/ea_implement.rb +2 -2
- data/lib/ea/qea/models/ea_object_effort.rb +2 -2
- data/lib/ea/qea/models/ea_object_problem.rb +2 -2
- data/lib/ea/qea/models/ea_object_require.rb +2 -2
- data/lib/ea/qea/models/ea_object_resource.rb +2 -2
- data/lib/ea/qea/models/ea_object_risk.rb +2 -2
- data/lib/ea/qea/models/ea_object_scenario.rb +2 -2
- data/lib/ea/qea/models/ea_object_test.rb +2 -2
- data/lib/ea/qea/models/ea_object_trx.rb +2 -2
- data/lib/ea/qea/models/ea_palette_item.rb +2 -2
- data/lib/ea/qea/models/ea_role_constraint.rb +2 -2
- data/lib/ea/qea/models/ea_secrypt.rb +2 -2
- data/lib/ea/qea/validation/database/ocl_constraint_validator.rb +65 -0
- data/lib/ea/qea/validation/database.rb +2 -0
- data/lib/ea/qea/validation/validation_engine.rb +2 -0
- data/lib/ea/qea/validation.rb +2 -0
- data/lib/ea/query/builder.rb +22 -3
- data/lib/ea/svg/ea_emitter/compartment/stereotype_icon.rb +32 -0
- data/lib/ea/svg/ea_emitter/compartment.rb +2 -0
- data/lib/ea/svg/ea_emitter/document.rb +33 -0
- data/lib/ea/svg/ea_emitter/element/stereotype_icon_renderer.rb +49 -12
- data/lib/ea/transformers/qea_to_xmi/transformer.rb +138 -2
- data/lib/ea/transformers.rb +3 -2
- data/lib/ea/validation/xmi_parity.rb +80 -0
- data/lib/ea/validation.rb +12 -0
- data/lib/ea/version.rb +1 -1
- data/lib/ea.rb +1 -0
- metadata +24 -3
- data/lib/ea/export/xmi/generator.rb +0 -77
- data/lib/ea/export/xmi.rb +0 -11
|
@@ -28,22 +28,63 @@ module Ea
|
|
|
28
28
|
OFFSET_X = 0
|
|
29
29
|
OFFSET_Y = 0
|
|
30
30
|
|
|
31
|
-
attr_reader :classifier, :bounds, :canvas
|
|
31
|
+
attr_reader :classifier, :bounds, :canvas, :mdg_registry
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
# @param mdg_registry [Ea::Mdg::Registry, nil] optional
|
|
34
|
+
# registry to look up MDG-provided ShapeScript for the
|
|
35
|
+
# classifier's stereotype. When provided and the matching
|
|
36
|
+
# stereotype has a ShapeScript body, it's parsed and
|
|
37
|
+
# rendered. Otherwise falls back to FALLBACK_ICONS.
|
|
38
|
+
def initialize(classifier:, bounds:, canvas: nil, mdg_registry: nil)
|
|
34
39
|
@classifier = classifier
|
|
35
40
|
@bounds = bounds
|
|
36
41
|
@canvas = canvas
|
|
42
|
+
@mdg_registry = mdg_registry
|
|
37
43
|
end
|
|
38
44
|
|
|
39
|
-
def self.render(classifier:, bounds:, canvas: nil, **
|
|
40
|
-
new(classifier: classifier, bounds: bounds, canvas: canvas).to_svg
|
|
45
|
+
def self.render(classifier:, bounds:, canvas: nil, **opts)
|
|
46
|
+
new(classifier: classifier, bounds: bounds, canvas: canvas, **opts).to_svg
|
|
41
47
|
end
|
|
42
48
|
|
|
43
|
-
# @return [String] SVG
|
|
49
|
+
# @return [String] SVG fragment, or "" if no icon applies
|
|
44
50
|
def to_svg
|
|
45
51
|
return "" unless classifier && bounds
|
|
52
|
+
return "" unless stereotype_name
|
|
46
53
|
|
|
54
|
+
shapescript_svg || fallback_svg
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# Try MDG-provided ShapeScript first.
|
|
60
|
+
# @return [String, nil] SVG fragment from parsed ShapeScript
|
|
61
|
+
def shapescript_svg
|
|
62
|
+
return nil unless mdg_registry
|
|
63
|
+
|
|
64
|
+
body = lookup_shapescript(stereotype_name)
|
|
65
|
+
return nil unless body
|
|
66
|
+
|
|
67
|
+
shapes = Ea::Shapescript::Parser.parse(body)
|
|
68
|
+
return nil if shapes.empty?
|
|
69
|
+
|
|
70
|
+
Ea::Shapescript::Renderer.render(shapes, fill: "#FAF1EC",
|
|
71
|
+
stroke: "#69738C")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @return [String, nil] ShapeScript source for the stereotype, if any
|
|
75
|
+
def lookup_shapescript(name)
|
|
76
|
+
mdg_registry.documents.each do |doc|
|
|
77
|
+
stereo = (doc.stereotypes || []).find { |s| s.name == name }
|
|
78
|
+
next unless stereo
|
|
79
|
+
|
|
80
|
+
notes = stereo.notes
|
|
81
|
+
return notes if notes && notes.include?("shape")
|
|
82
|
+
end
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @return [String] hardcoded fallback polygon, or "" if none
|
|
87
|
+
def fallback_svg
|
|
47
88
|
spec = FALLBACK_ICONS[stereotype_name]
|
|
48
89
|
return "" unless spec
|
|
49
90
|
|
|
@@ -55,15 +96,11 @@ module Ea
|
|
|
55
96
|
%(<polygon points="#{pts_str}" fill="#{fill}" stroke="#{stroke}" stroke-width="1"/>)
|
|
56
97
|
end
|
|
57
98
|
|
|
58
|
-
private
|
|
59
|
-
|
|
60
99
|
def stereotype_name
|
|
61
|
-
|
|
62
|
-
return refs.first if refs&.any?
|
|
100
|
+
return nil unless classifier.is_a?(Ea::Model::Classifier)
|
|
63
101
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
nil
|
|
102
|
+
refs = classifier.stereotype_refs
|
|
103
|
+
refs&.first
|
|
67
104
|
end
|
|
68
105
|
|
|
69
106
|
def translate_point(x, y)
|
|
@@ -70,8 +70,144 @@ module Ea
|
|
|
70
70
|
# nil-valued attributes are skipped at the source. No
|
|
71
71
|
# post-processing pass is needed — the prior XmlSanitizer
|
|
72
72
|
# workaround (TODO 21 §1) has been removed.
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
# Serialize to XMI XML.
|
|
74
|
+
#
|
|
75
|
+
# Default produces model tree only (parseable by the xmi gem's
|
|
76
|
+
# Sparx parser for round-trip). Pass `with_extensions: true` to
|
|
77
|
+
# also emit EA-specific `<connectors>` and `<diagrams>` sections
|
|
78
|
+
# inside `<xmi:Extension>`. Those use post-processing string
|
|
79
|
+
# injection that the xmi gem's parser doesn't model, so the
|
|
80
|
+
# output is NOT round-trippable when extensions are included.
|
|
81
|
+
#
|
|
82
|
+
# @param with_extensions [Boolean]
|
|
83
|
+
# @return [String] XMI XML
|
|
84
|
+
def serialize(with_extensions: true)
|
|
85
|
+
xml = build_root.to_xml(use_prefix: true)
|
|
86
|
+
with_extensions ? inject_extension_content(xml) : xml
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Injects EA-specific extension content (connectors, diagrams)
|
|
90
|
+
# into the serialized XMI. The xmi gem's Extension type is
|
|
91
|
+
# modeled but doesn't declare `connectors` / `diagrams`
|
|
92
|
+
# children. We post-process the serialized XML to insert
|
|
93
|
+
# them inside the existing `<xmi:Extension>` element.
|
|
94
|
+
def inject_extension_content(xml)
|
|
95
|
+
content = extension_inner_xml
|
|
96
|
+
return xml if content.empty?
|
|
97
|
+
|
|
98
|
+
# Replace the empty `<xmi:Extension .../>` or the
|
|
99
|
+
# closing `</xmi:Extension>` with the populated version.
|
|
100
|
+
if xml.include?("<xmi:Extension")
|
|
101
|
+
xml.sub(%r{<xmi:Extension ([^>]+)>\s*</xmi:Extension>},
|
|
102
|
+
"<xmi:Extension \\1>\n#{content}\n\t</xmi:Extension>")
|
|
103
|
+
.sub(/<xmi:Extension ([^>]+)\/>/,
|
|
104
|
+
"<xmi:Extension \\1>\n#{content}\n\t</xmi:Extension>")
|
|
105
|
+
else
|
|
106
|
+
xml
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @return [String] XML for the inner content of <xmi:Extension>:
|
|
111
|
+
# <elements>...</elements>, <connectors>...</connectors>,
|
|
112
|
+
# <diagrams>...</diagrams>. Empty when none apply.
|
|
113
|
+
def extension_inner_xml
|
|
114
|
+
parts = []
|
|
115
|
+
connectors_xml = build_connectors_extension
|
|
116
|
+
parts << connectors_xml unless connectors_xml.empty?
|
|
117
|
+
diagrams_xml = build_diagrams_extension
|
|
118
|
+
parts << diagrams_xml unless diagrams_xml.empty?
|
|
119
|
+
parts.join("\n")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def build_connectors_extension
|
|
123
|
+
connectors = @database.collections[:connectors] || []
|
|
124
|
+
return "" if connectors.empty?
|
|
125
|
+
|
|
126
|
+
inner = connectors.map { |conn| connector_xml(conn) }.compact.join("\n")
|
|
127
|
+
return "" if inner.empty?
|
|
128
|
+
|
|
129
|
+
"\t\t<connectors>\n#{inner}\n\t\t</connectors>"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def build_diagrams_extension
|
|
133
|
+
diagrams = @database.collections[:diagrams] || []
|
|
134
|
+
return "" if diagrams.empty?
|
|
135
|
+
|
|
136
|
+
inner = diagrams.map { |dgm| diagram_xml(dgm) }.compact.join("\n")
|
|
137
|
+
return "" if inner.empty?
|
|
138
|
+
|
|
139
|
+
"\t\t<diagrams>\n#{inner}\n\t\t</diagrams>"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Per-connector XML with xmi:idref to the model-level
|
|
143
|
+
# association (when present) plus source/target references.
|
|
144
|
+
def connector_xml(conn)
|
|
145
|
+
return nil unless conn.ea_guid
|
|
146
|
+
|
|
147
|
+
connector_id = "EAID_#{guid_to_xmi_id(conn.ea_guid)}"
|
|
148
|
+
source_obj = @database.collections[:objects]&.find { |o| o.ea_object_id == conn.start_object_id }
|
|
149
|
+
target_obj = @database.collections[:objects]&.find { |o| o.ea_object_id == conn.end_object_id }
|
|
150
|
+
return nil unless source_obj && target_obj
|
|
151
|
+
|
|
152
|
+
name_attr = conn.name ? %( name="#{escape(conn.name)}") : ""
|
|
153
|
+
inner = source_end_xml(conn, source_obj) + target_end_xml(conn, target_obj)
|
|
154
|
+
%(<connector xmi:idref="#{connector_id}"#{name_attr}>\n#{inner}\t\t</connector>)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def source_end_xml(conn, source_obj)
|
|
158
|
+
end_xml(source_obj, "source")
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def target_end_xml(conn, target_obj)
|
|
162
|
+
end_xml(target_obj, "target")
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def end_xml(obj, kind)
|
|
166
|
+
xmi_id = "EAID_#{guid_to_xmi_id(obj.ea_guid)}"
|
|
167
|
+
lines = [
|
|
168
|
+
"\t\t\t<#{kind} xmi:idref=\"#{xmi_id}\">",
|
|
169
|
+
"\t\t\t\t<model ea_localid=\"#{obj.ea_object_id}\" " \
|
|
170
|
+
"type=\"#{obj.object_type}\" name=\"#{escape(obj.name.to_s)}\"/>",
|
|
171
|
+
"\t\t\t\t<role visibility=\"Public\" targetScope=\"instance\"/>",
|
|
172
|
+
"\t\t\t</#{kind}>"
|
|
173
|
+
]
|
|
174
|
+
lines.join("\n") + "\n"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def diagram_xml(dgm)
|
|
178
|
+
return nil unless dgm.ea_guid
|
|
179
|
+
|
|
180
|
+
diagram_id = "EAID_#{guid_to_xmi_id(dgm.ea_guid)}"
|
|
181
|
+
pkg_guid = package_guid_for(dgm)
|
|
182
|
+
pkg_ref = pkg_guid ? "EAPK_#{guid_to_xmi_id(pkg_guid)}" : ""
|
|
183
|
+
lines = [
|
|
184
|
+
"<diagram xmi:id=\"#{diagram_id}\">",
|
|
185
|
+
"\t\t\t<model package=\"#{pkg_ref}\" localID=\"#{dgm.diagram_id}\" owner=\"#{pkg_ref}\"/>",
|
|
186
|
+
"\t\t\t<properties name=\"#{escape(dgm.name.to_s)}\" type=\"#{escape(dgm.diagram_type.to_s)}\"/>",
|
|
187
|
+
"\t\t</diagram>"
|
|
188
|
+
]
|
|
189
|
+
lines.join("\n")
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def package_guid_for(dgm)
|
|
193
|
+
pkg = (@database.collections[:packages] || []).find { |p| p.package_id == dgm.package_id }
|
|
194
|
+
pkg&.ea_guid || dgm.ea_guid
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def guid_to_xmi_id(guid)
|
|
198
|
+
return "" unless guid
|
|
199
|
+
|
|
200
|
+
guid.to_s.gsub(/[{}]/, "")
|
|
201
|
+
.tr("-", "_")
|
|
202
|
+
.prepend("EA") # truncated below
|
|
203
|
+
.sub(/\AEA/, "")
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def escape(text)
|
|
207
|
+
text.to_s.gsub("&", "&")
|
|
208
|
+
.gsub("<", "<")
|
|
209
|
+
.gsub(">", ">")
|
|
210
|
+
.gsub('"', """)
|
|
75
211
|
end
|
|
76
212
|
|
|
77
213
|
private
|
data/lib/ea/transformers.rb
CHANGED
|
@@ -25,9 +25,10 @@ module Ea
|
|
|
25
25
|
# Sparx-specific concepts (stereotypes, tagged values, multiplicities,
|
|
26
26
|
# diagrams, xrefs).
|
|
27
27
|
# @param database [Ea::Qea::Database]
|
|
28
|
+
# @param with_extensions [Boolean] emit EA-specific connectors+diagrams
|
|
28
29
|
# @return [String] XMI XML
|
|
29
|
-
def qea_to_xmi(database)
|
|
30
|
-
QeaToXmi::Transformer.new(database).serialize
|
|
30
|
+
def qea_to_xmi(database, with_extensions: true)
|
|
31
|
+
QeaToXmi::Transformer.new(database).serialize(with_extensions: with_extensions)
|
|
31
32
|
end
|
|
32
33
|
end
|
|
33
34
|
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Validation
|
|
5
|
+
# Compares Ea::Transformers.qea_to_xmi output against EA's
|
|
6
|
+
# reference XMI export (`examples/exports/*/model.xml`).
|
|
7
|
+
#
|
|
8
|
+
# Reports per-element-type counts (packagedElement, ownedAttribute,
|
|
9
|
+
# ownedEnd, memberEnd, generalization, connector, diagram, style,
|
|
10
|
+
# tags, documentation, xmi:Extension) so gaps surface clearly.
|
|
11
|
+
#
|
|
12
|
+
# Used by the parity regression spec to track XMI export fidelity
|
|
13
|
+
# over time. Improvements to QeaToXmi should shrink the gaps.
|
|
14
|
+
class XmiParity
|
|
15
|
+
ELEMENT_TYPES = %w[
|
|
16
|
+
packagedElement
|
|
17
|
+
ownedAttribute
|
|
18
|
+
ownedOperation
|
|
19
|
+
ownedEnd
|
|
20
|
+
memberEnd
|
|
21
|
+
generalization
|
|
22
|
+
connector
|
|
23
|
+
diagram
|
|
24
|
+
taggedValue
|
|
25
|
+
xmi:Extension
|
|
26
|
+
style
|
|
27
|
+
tags
|
|
28
|
+
documentation
|
|
29
|
+
].freeze
|
|
30
|
+
|
|
31
|
+
# Per-element counts for one XMI document.
|
|
32
|
+
Counts = Struct.new(:total, :by_type, keyword_init: true) do
|
|
33
|
+
def initialize(*)
|
|
34
|
+
super
|
|
35
|
+
self.by_type ||= {}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def delta(other)
|
|
39
|
+
result = { total: total - other.total }
|
|
40
|
+
ELEMENT_TYPES.each do |t|
|
|
41
|
+
result[t] = (by_type[t] || 0) - (other.by_type[t] || 0)
|
|
42
|
+
end
|
|
43
|
+
result
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @param qea_path [String] path to the QEA file
|
|
48
|
+
# @param reference_xmi_path [String] path to EA's reference XMI
|
|
49
|
+
# @return [Hash] {:ours, :reference, :delta} each a Counts
|
|
50
|
+
def self.compare(qea_path, reference_xmi_path)
|
|
51
|
+
database = Ea::Qea.load(qea_path)
|
|
52
|
+
ours_xml = Ea::Transformers.qea_to_xmi(database)
|
|
53
|
+
ref_xml = File.read(reference_xmi_path)
|
|
54
|
+
|
|
55
|
+
{
|
|
56
|
+
qea: qea_path,
|
|
57
|
+
reference: reference_xmi_path,
|
|
58
|
+
ours: count(ours_xml),
|
|
59
|
+
reference_counts: count(ref_xml),
|
|
60
|
+
delta: count(ours_xml).delta(count(ref_xml))
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @param xml [String] XMI markup (any encoding)
|
|
65
|
+
# @return [Counts]
|
|
66
|
+
def self.count(xml)
|
|
67
|
+
# EA exports use windows-1252; force UTF-8 for safe regex.
|
|
68
|
+
safe = xml.to_s.encode("UTF-8", invalid: :replace, undef: :replace,
|
|
69
|
+
replace: "?")
|
|
70
|
+
by_type = ELEMENT_TYPES.each_with_object({}) do |tag, acc|
|
|
71
|
+
# Match `<tag>` followed by space, `/`, or `>` (covers
|
|
72
|
+
# `<tag/>`, `<tag foo=...>`, `<tag>`). Using %r{} to avoid
|
|
73
|
+
# `/` regex-delimiter conflict with the `/` in the class.
|
|
74
|
+
acc[tag] = safe.scan(%r{<#{tag}[\s/>]}).size
|
|
75
|
+
end
|
|
76
|
+
Counts.new(total: by_type.values.sum, by_type: by_type)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
# Top-level validation namespace. Hosts cross-cutting validators
|
|
5
|
+
# that span multiple subsystems (QEA + XMI, lint rules, OCL).
|
|
6
|
+
#
|
|
7
|
+
# Distinct from Ea::Qea::Validation which is specific to QEA
|
|
8
|
+
# database + UML document structural validation.
|
|
9
|
+
module Validation
|
|
10
|
+
autoload :XmiParity, "ea/validation/xmi_parity"
|
|
11
|
+
end
|
|
12
|
+
end
|
data/lib/ea/version.rb
CHANGED
data/lib/ea.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ea
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -223,6 +223,25 @@ files:
|
|
|
223
223
|
- TODO.complete/45-shapescript-extensions.md
|
|
224
224
|
- TODO.complete/46-xsd-consolidation.md
|
|
225
225
|
- TODO.complete/47-headerlines-legacy-removal.md
|
|
226
|
+
- TODO.complete/51-remove-respond-to.md
|
|
227
|
+
- TODO.complete/52-xsd-decouple-fixtures.md
|
|
228
|
+
- TODO.complete/53-xmi-public-send-cleanup.md
|
|
229
|
+
- TODO.complete/54-wire-stereotype-icon-renderer.md
|
|
230
|
+
- TODO.complete/55-wire-shapescript.md
|
|
231
|
+
- TODO.complete/56-wire-emf-renderer.md
|
|
232
|
+
- TODO.complete/57-wire-ocl-evaluator.md
|
|
233
|
+
- TODO.complete/58-diff-modifications.md
|
|
234
|
+
- TODO.complete/59-delete-toy-xmi-export.md
|
|
235
|
+
- TODO.complete/60-curate-json-export.md
|
|
236
|
+
- TODO.complete/61-plantuml-relationships.md
|
|
237
|
+
- TODO.complete/62-changelog.md
|
|
238
|
+
- TODO.complete/63-dependabot-fix.md
|
|
239
|
+
- TODO.complete/64-qeatoxmi-connectors.md
|
|
240
|
+
- TODO.complete/65-qeatoxmi-diagrams.md
|
|
241
|
+
- TODO.complete/66-qeatoxmi-style-tags-docs.md
|
|
242
|
+
- TODO.complete/67-ocl-collection-ops.md
|
|
243
|
+
- TODO.complete/68-ocl-comparison-ops.md
|
|
244
|
+
- TODO.complete/69-spec-new-models.md
|
|
226
245
|
- TODO.complete/STATUS.md
|
|
227
246
|
- TODO.diagrams/01-svg-measurement-harness.md
|
|
228
247
|
- TODO.diagrams/01-xmi-extension-elements-parsing.md
|
|
@@ -586,8 +605,6 @@ files:
|
|
|
586
605
|
- lib/ea/export/json_schema/generator.rb
|
|
587
606
|
- lib/ea/export/plantuml.rb
|
|
588
607
|
- lib/ea/export/plantuml/generator.rb
|
|
589
|
-
- lib/ea/export/xmi.rb
|
|
590
|
-
- lib/ea/export/xmi/generator.rb
|
|
591
608
|
- lib/ea/export/xsd.rb
|
|
592
609
|
- lib/ea/export/xsd/class_mapping.rb
|
|
593
610
|
- lib/ea/export/xsd/generator.rb
|
|
@@ -753,6 +770,7 @@ files:
|
|
|
753
770
|
- lib/ea/qea/validation/class_validator.rb
|
|
754
771
|
- lib/ea/qea/validation/database.rb
|
|
755
772
|
- lib/ea/qea/validation/database/circular_reference_validator.rb
|
|
773
|
+
- lib/ea/qea/validation/database/ocl_constraint_validator.rb
|
|
756
774
|
- lib/ea/qea/validation/database/orphan_validator.rb
|
|
757
775
|
- lib/ea/qea/validation/database/referential_integrity_validator.rb
|
|
758
776
|
- lib/ea/qea/validation/diagram_validator.rb
|
|
@@ -862,6 +880,7 @@ files:
|
|
|
862
880
|
- lib/ea/svg/ea_emitter/compartment/package_contents.rb
|
|
863
881
|
- lib/ea/svg/ea_emitter/compartment/package_from_parent.rb
|
|
864
882
|
- lib/ea/svg/ea_emitter/compartment/shape.rb
|
|
883
|
+
- lib/ea/svg/ea_emitter/compartment/stereotype_icon.rb
|
|
865
884
|
- lib/ea/svg/ea_emitter/compartment/tagged_values.rb
|
|
866
885
|
- lib/ea/svg/ea_emitter/connectors.rb
|
|
867
886
|
- lib/ea/svg/ea_emitter/diagram_frame.rb
|
|
@@ -940,6 +959,8 @@ files:
|
|
|
940
959
|
- lib/ea/transformers/uml_to_xmi/id_generator.rb
|
|
941
960
|
- lib/ea/transformers/uml_to_xmi/transformer.rb
|
|
942
961
|
- lib/ea/transformers/uml_to_xmi/writer.rb
|
|
962
|
+
- lib/ea/validation.rb
|
|
963
|
+
- lib/ea/validation/xmi_parity.rb
|
|
943
964
|
- lib/ea/version.rb
|
|
944
965
|
- lib/ea/xmi.rb
|
|
945
966
|
- lib/ea/xmi/liquid_drops/association_drop.rb
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "nokogiri"
|
|
4
|
-
|
|
5
|
-
module Ea
|
|
6
|
-
module Export
|
|
7
|
-
module Xmi
|
|
8
|
-
# Generates Sparx-flavored XMI 2.1 from a parsed QEA model.
|
|
9
|
-
#
|
|
10
|
-
# Uses the existing QEA→XMI transformer under Ea::Transformers
|
|
11
|
-
# when available. Falls back to a minimal hand-rolled writer
|
|
12
|
-
# that produces a valid XMI root + class list when the bridge
|
|
13
|
-
# is unavailable.
|
|
14
|
-
class Generator
|
|
15
|
-
def self.call(model, **_opts)
|
|
16
|
-
new(model).call
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
attr_reader :model
|
|
20
|
-
|
|
21
|
-
# @param model [#collections] Ea::Qea::Database or compatible
|
|
22
|
-
def initialize(model)
|
|
23
|
-
@model = model
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def call
|
|
27
|
-
builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
|
|
28
|
-
xml.xmi("XMI" => "2.1",
|
|
29
|
-
"xmlns:UML" => "http://schema.omg.org/spec/UML/2.1",
|
|
30
|
-
"xmlns:xmi" => "http://schema.omg.org/spec/XMI/2.1") do
|
|
31
|
-
xml.documentation(:exporter => "ea-rb",
|
|
32
|
-
:exporterVersion => Ea::VERSION)
|
|
33
|
-
emit_model(xml)
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
builder.to_xml
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
private
|
|
40
|
-
|
|
41
|
-
def emit_model(xml)
|
|
42
|
-
classes = (model.collections[:objects] || [])
|
|
43
|
-
.select { |o| o.object_type == "Class" }
|
|
44
|
-
|
|
45
|
-
xml.tag!("uml:Model", "xmi:id" => "model-root",
|
|
46
|
-
"name" => "model") do
|
|
47
|
-
classes.each do |klass|
|
|
48
|
-
emit_class(xml, klass)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def emit_class(xml, klass)
|
|
54
|
-
attrs = class_attributes(klass)
|
|
55
|
-
xml.tag!("ownedMember",
|
|
56
|
-
"xmi:type" => "uml:Class",
|
|
57
|
-
"xmi:id" => klass.ea_guid || klass.object_id.to_s,
|
|
58
|
-
"name" => klass.name || "Anonymous") do
|
|
59
|
-
attrs.each { |attr| emit_attribute(xml, attr) }
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def emit_attribute(xml, attr)
|
|
64
|
-
xml.tag!("ownedAttribute",
|
|
65
|
-
"xmi:type" => "uml:Property",
|
|
66
|
-
"xmi:id" => attr.ea_guid || attr.property_id.to_s,
|
|
67
|
-
"name" => attr.name)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def class_attributes(klass)
|
|
71
|
-
attrs = model.collections[:attributes] || []
|
|
72
|
-
attrs.select { |a| a.object_id == klass.object_id }
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|