ea 0.5.2 → 0.6.0
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.complete/01-t-xref-parsing.md +1 -1
- data/TODO.complete/02-per-label-geometry.md +1 -1
- data/TODO.complete/03-object-properties-constraints.md +1 -1
- data/TODO.complete/04-operation-params.md +1 -1
- data/TODO.complete/05-pdata-flag-matrix.md +1 -1
- data/TODO.complete/06-emf-image-rendering.md +1 -1
- data/TODO.complete/07-auxiliary-tables.md +1 -1
- data/TODO.complete/10-mdg-stereotype-registry.md +1 -1
- data/TODO.complete/11-gml-xsd-generation.md +1 -1
- data/TODO.complete/13-shapescript-interpreter.md +12 -23
- data/TODO.complete/20-cli-diff.md +1 -1
- data/TODO.complete/21-cli-render.md +1 -1
- data/TODO.complete/22-cli-export.md +1 -1
- data/TODO.complete/23-cli-mdg.md +1 -1
- data/TODO.complete/30-headerlines-ocp-refactor.md +1 -1
- data/TODO.complete/32-spec-coverage.md +1 -1
- data/TODO.complete/33-blocked-parity-gaps.md +1 -1
- data/TODO.complete/36-stereotype-icon-wiring.md +1 -1
- data/TODO.complete/37-cli-lint.md +1 -1
- data/TODO.complete/38-cli-query.md +1 -1
- data/TODO.complete/39-cli-info.md +1 -1
- data/TODO.complete/40-ocl-evaluator.md +1 -1
- data/TODO.complete/41-round-trip-spec.md +1 -1
- data/TODO.complete/42-html-diff-report.md +1 -1
- data/TODO.complete/43-json-schema-export.md +1 -1
- data/TODO.complete/44-remaining-qea-tables.md +1 -1
- data/TODO.complete/45-shapescript-extensions.md +1 -1
- data/TODO.complete/46-xsd-consolidation.md +1 -1
- data/TODO.complete/47-headerlines-legacy-removal.md +1 -1
- data/TODO.complete/51-remove-respond-to.md +42 -15
- data/TODO.complete/52-xsd-decouple-fixtures.md +1 -1
- data/TODO.complete/54-wire-stereotype-icon-renderer.md +1 -1
- data/TODO.complete/55-wire-shapescript.md +1 -1
- data/TODO.complete/56-wire-emf-renderer.md +1 -1
- data/TODO.complete/57-wire-ocl-evaluator.md +1 -1
- data/TODO.complete/58-diff-modifications.md +1 -1
- data/TODO.complete/59-delete-toy-xmi-export.md +1 -1
- data/TODO.complete/60-curate-json-export.md +1 -1
- data/TODO.complete/61-plantuml-relationships.md +1 -1
- data/TODO.complete/62-changelog.md +1 -1
- data/TODO.complete/63-dependabot-fix.md +1 -1
- data/TODO.complete/64-qeatoxmi-connectors.md +9 -24
- data/TODO.complete/65-qeatoxmi-diagrams.md +9 -23
- data/TODO.complete/66-qeatoxmi-style-tags-docs.md +25 -18
- data/TODO.complete/67-ocl-collection-ops.md +12 -12
- data/TODO.complete/68-ocl-comparison-ops.md +16 -11
- data/TODO.complete/69-spec-new-models.md +13 -18
- data/TODO.complete/70-specs-for-all-models.md +21 -0
- data/TODO.complete/71-autoload-audit.md +26 -0
- data/TODO.complete/72-extension-serializer-ocp-naming.md +26 -0
- data/TODO.complete/73-ocl-validator-latent-bugs.md +50 -0
- data/TODO.complete/74-visibility-symbol-dry.md +39 -0
- data/TODO.complete/75-database-table-collection-registry.md +32 -0
- data/TODO.complete/76-stale-todo-cleanup.md +52 -0
- data/TODO.complete/77-eliminate-banned-patterns-from-specs.md +55 -0
- data/TODO.complete/78-mdg-stereotype-emission.md +66 -0
- data/TODO.complete/79-equivalence-fixes.md +57 -0
- data/TODO.complete/80-performance-extension-serializer-indexes.md +43 -0
- data/TODO.complete/81-dry-scope-guid-extraction.md +33 -0
- data/TODO.complete/82-public-send-audit-conclusion.md +25 -0
- data/TODO.complete/83-dry-shared-xml-escape-guid-format.md +52 -0
- data/TODO.complete/84-ocp-markers-shape-registry.md +54 -0
- data/TODO.complete/85-frozen-literal-rubocop-normalize-type-spec.md +38 -0
- data/TODO.complete/86-frozen-sweep-lint-query-specs.md +55 -0
- data/TODO.complete/STATUS.md +26 -5
- data/lib/ea/cli/app.rb +1 -0
- data/lib/ea/cli/command/export.rb +10 -1
- data/lib/ea/cli/command/mdg.rb +1 -10
- data/lib/ea/cli/command/render.rb +2 -1
- data/lib/ea/diff/html_reporter.rb +1 -5
- data/lib/ea/export/json/generator.rb +5 -6
- data/lib/ea/export/plantuml/generator.rb +5 -3
- data/lib/ea/guid_format.rb +91 -0
- data/lib/ea/mdg/document.rb +2 -1
- data/lib/ea/mdg/loader.rb +2 -1
- data/lib/ea/mdg/registry.rb +57 -2
- data/lib/ea/mdg/xml/applies_to.rb +1 -1
- data/lib/ea/mdg/xml/apply.rb +1 -1
- data/lib/ea/mdg/xml/content.rb +1 -1
- data/lib/ea/mdg/xml/documentation.rb +1 -1
- data/lib/ea/mdg/xml/stereotype.rb +1 -1
- data/lib/ea/mdg/xml/stereotypes.rb +1 -1
- data/lib/ea/mdg/xml/tag.rb +1 -1
- data/lib/ea/mdg/xml/tagged_values.rb +1 -1
- data/lib/ea/mdg/xml/technology.rb +1 -1
- data/lib/ea/mdg/xml/uml_profile.rb +1 -1
- data/lib/ea/mdg/xml/uml_profiles.rb +1 -1
- data/lib/ea/mdg/xml.rb +2 -1
- data/lib/ea/mdg.rb +2 -1
- data/lib/ea/model/ghost_label.rb +2 -1
- data/lib/ea/ocl/evaluator.rb +4 -2
- data/lib/ea/qea/database.rb +57 -2
- data/lib/ea/qea/factory/reference_resolver.rb +1 -1
- data/lib/ea/qea/models/ea_attribute.rb +1 -17
- data/lib/ea/qea/models/ea_connector.rb +3 -2
- data/lib/ea/qea/models/ea_object.rb +14 -9
- data/lib/ea/qea/models/ea_operation.rb +1 -17
- data/lib/ea/qea/models/ea_xref.rb +3 -0
- data/lib/ea/qea/models/scope_predicate.rb +29 -0
- data/lib/ea/qea/models.rb +1 -0
- data/lib/ea/qea/repositories/base_repository.rb +4 -1
- data/lib/ea/qea/validation/base_validator.rb +3 -48
- data/lib/ea/qea/validation/database/ocl_constraint_validator.rb +23 -13
- data/lib/ea/shapescript/renderer.rb +1 -3
- data/lib/ea/sources/qea/diagram_builder.rb +1 -0
- data/lib/ea/sources/qea/id_normalizer.rb +2 -2
- data/lib/ea/svg/ea_emitter/background.rb +2 -1
- data/lib/ea/svg/ea_emitter/element/attribute_line_builder.rb +1 -6
- data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +2 -7
- data/lib/ea/svg/ea_emitter/elements.rb +1 -3
- data/lib/ea/svg/ea_emitter/ghost_labels.rb +2 -1
- data/lib/ea/svg/ea_emitter/label/end_label.rb +1 -6
- data/lib/ea/svg/ea_emitter/marker/package_import.rb +1 -1
- data/lib/ea/svg/ea_emitter/markers.rb +52 -23
- data/lib/ea/svg/ea_emitter/text_renderer.rb +1 -4
- data/lib/ea/svg/ea_emitter/visibility_symbol.rb +37 -0
- data/lib/ea/svg/ea_emitter.rb +1 -0
- data/lib/ea/svg/parity/source.rb +0 -2
- data/lib/ea/svg/parity/suite.rb +0 -1
- data/lib/ea/transformers/qea_to_xmi/cardinality.rb +40 -28
- data/lib/ea/transformers/qea_to_xmi/context.rb +35 -1
- data/lib/ea/transformers/qea_to_xmi/extension_serializer.rb +686 -0
- data/lib/ea/transformers/qea_to_xmi/guid_format.rb +5 -48
- data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +55 -54
- data/lib/ea/transformers/qea_to_xmi/primitive_types.rb +105 -0
- data/lib/ea/transformers/qea_to_xmi/profile_serializer.rb +163 -0
- data/lib/ea/transformers/qea_to_xmi/run_state.rb +7 -6
- data/lib/ea/transformers/qea_to_xmi/transformer.rb +396 -185
- data/lib/ea/transformers/qea_to_xmi/visibility.rb +30 -19
- data/lib/ea/transformers/qea_to_xmi.rb +5 -2
- data/lib/ea/transformers.rb +7 -2
- data/lib/ea/validation.rb +1 -1
- data/lib/ea/version.rb +1 -1
- data/lib/ea/xmi/liquid_drops/attribute_drop.rb +12 -4
- data/lib/ea/xmi/liquid_drops/klass_drop.rb +1 -2
- data/lib/ea/xmi/lookup_service.rb +4 -0
- data/lib/ea/xmi/parser.rb +30 -1
- data/lib/ea/xml_escape.rb +21 -0
- data/lib/ea.rb +2 -0
- metadata +28 -11
- data/lutaml-uml-0.5.1.gem +0 -0
|
@@ -0,0 +1,686 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Transformers
|
|
5
|
+
module QeaToXmi
|
|
6
|
+
# Serializes the EA-specific content of `<xmi:Extension>`.
|
|
7
|
+
#
|
|
8
|
+
# The xmi gem models the structural XMI tree (model, packages,
|
|
9
|
+
# classes, attributes) but does not represent EA's per-element
|
|
10
|
+
# extension metadata. This class produces the inner XML for the
|
|
11
|
+
# `<xmi:Extension>` element as three sibling sections:
|
|
12
|
+
#
|
|
13
|
+
# <elements> — one <element> per package/classifier, carrying
|
|
14
|
+
# <model>, <properties documentation="...">,
|
|
15
|
+
# <style appearance="..."> and nested <tags>.
|
|
16
|
+
# <connectors> — one <connector> per relationship, with
|
|
17
|
+
# source/target <model> blocks.
|
|
18
|
+
# <diagrams> — one <diagram> per diagram, with placed
|
|
19
|
+
# <element> children referencing subjects.
|
|
20
|
+
#
|
|
21
|
+
# The Transformer injects this string into the serialized XMI's
|
|
22
|
+
# empty `<xmi:Extension>` placeholder.
|
|
23
|
+
#
|
|
24
|
+
# Sparx XMI is the canonical shape; the xmi gem's parser does not
|
|
25
|
+
# model these sections, so output containing them is not
|
|
26
|
+
# round-trippable through `Xmi::Sparx::Root.parse_xml`.
|
|
27
|
+
class ExtensionSerializer
|
|
28
|
+
def initialize(database, context)
|
|
29
|
+
@database = database
|
|
30
|
+
@context = context
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [String] inner XML for <xmi:Extension>; empty if no
|
|
34
|
+
# elements/connectors/diagrams to emit.
|
|
35
|
+
def call
|
|
36
|
+
sections = [
|
|
37
|
+
build_elements_section,
|
|
38
|
+
build_connectors_section,
|
|
39
|
+
build_primitivetypes_section,
|
|
40
|
+
build_diagrams_section
|
|
41
|
+
].reject(&:empty?)
|
|
42
|
+
sections.join("\n")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# ---- <elements> section -----------------------------------------
|
|
48
|
+
|
|
49
|
+
def build_elements_section
|
|
50
|
+
entries = element_entries
|
|
51
|
+
return "" if entries.empty?
|
|
52
|
+
|
|
53
|
+
"\t\t<elements>\n#{entries.join("\n")}\n\t\t</elements>"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# One <element> block per package and per classifier object.
|
|
57
|
+
# Notes/Text/ProxyConnector objects are skipped — they have no
|
|
58
|
+
# standalone representation in the extension's <elements> list.
|
|
59
|
+
def element_entries
|
|
60
|
+
packages = @database.collections[:packages] || []
|
|
61
|
+
objects = @database.collections[:objects] || []
|
|
62
|
+
package_entries = packages.map { |pkg| package_element_xml(pkg) }
|
|
63
|
+
object_entries = objects.filter_map { |obj| object_element_xml(obj) }
|
|
64
|
+
(package_entries + object_entries).compact
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def package_element_xml(pkg)
|
|
68
|
+
idref = @context.xmi_id_for(pkg, prefix: "EAPK")
|
|
69
|
+
return nil unless idref
|
|
70
|
+
|
|
71
|
+
name = pkg.name.to_s
|
|
72
|
+
scope = "public"
|
|
73
|
+
lines = [
|
|
74
|
+
%(\t\t<element xmi:idref="#{idref}" xmi:type="uml:Package" name="#{escape(name)}" scope="#{scope}">),
|
|
75
|
+
model_for_package(pkg),
|
|
76
|
+
properties_for(record_documentation(pkg.notes), stype: "Package", scope: scope),
|
|
77
|
+
style_for_package(pkg),
|
|
78
|
+
tags_block_for(pkg.ea_guid),
|
|
79
|
+
"\t\t</element>"
|
|
80
|
+
]
|
|
81
|
+
lines.join("\n")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def object_element_xml(obj)
|
|
85
|
+
return nil if skip_object?(obj)
|
|
86
|
+
|
|
87
|
+
idref = @context.xmi_id_for(obj)
|
|
88
|
+
return nil unless idref
|
|
89
|
+
|
|
90
|
+
uml_type = uml_type_for(obj)
|
|
91
|
+
name = obj.name.to_s
|
|
92
|
+
scope = (obj.scope || "Public").downcase
|
|
93
|
+
lines = [
|
|
94
|
+
%(\t\t<element xmi:idref="#{idref}" xmi:type="#{uml_type}" name="#{escape(name)}" scope="#{scope}">),
|
|
95
|
+
model_for_object(obj),
|
|
96
|
+
properties_for(record_documentation(obj.note), stype: obj.object_type, scope: scope,
|
|
97
|
+
is_abstract: obj.abstract?),
|
|
98
|
+
style_for_object(obj),
|
|
99
|
+
tags_block_for(obj.ea_guid),
|
|
100
|
+
xrefs_xml("\t\t\t", obj.ea_guid, "element property"),
|
|
101
|
+
attributes_block_for(obj),
|
|
102
|
+
operations_block_for(obj),
|
|
103
|
+
"\t\t</element>"
|
|
104
|
+
]
|
|
105
|
+
lines.join("\n")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# EA object types that are diagram-only annotations or internal
|
|
109
|
+
# plumbing — they don't get a standalone <element> entry.
|
|
110
|
+
SKIP_OBJECT_TYPES = %w[Note Text ProxyConnector Boundary].freeze
|
|
111
|
+
|
|
112
|
+
def skip_object?(obj)
|
|
113
|
+
obj.object_type.nil? || SKIP_OBJECT_TYPES.include?(obj.object_type)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# OCP registry: EA object type → UML XMI type.
|
|
117
|
+
# Adding a new mapping = adding one entry, not editing a
|
|
118
|
+
# case/when branch.
|
|
119
|
+
UML_TYPE_FOR = {
|
|
120
|
+
"Class" => "uml:Class",
|
|
121
|
+
"Interface" => "uml:Interface",
|
|
122
|
+
"Enumeration" => "uml:Enumeration",
|
|
123
|
+
"DataType" => "uml:DataType",
|
|
124
|
+
"PrimitiveType" => "uml:PrimitiveType",
|
|
125
|
+
"Object" => "uml:Object",
|
|
126
|
+
"Signal" => "uml:Signal",
|
|
127
|
+
"Package" => "uml:Package",
|
|
128
|
+
"Association" => "uml:Association"
|
|
129
|
+
}.freeze
|
|
130
|
+
|
|
131
|
+
def uml_type_for(obj)
|
|
132
|
+
UML_TYPE_FOR[obj.object_type] || obj.object_type.to_s
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# ---- <model> / <properties> / <style> ---------------------------
|
|
136
|
+
|
|
137
|
+
def model_for_package(pkg)
|
|
138
|
+
parent = parent_package_ref(pkg)
|
|
139
|
+
owner = parent
|
|
140
|
+
attrs = [
|
|
141
|
+
%(package2="#{@context.xmi_id_for(pkg)}")
|
|
142
|
+
]
|
|
143
|
+
attrs << %(package="#{parent}") if parent
|
|
144
|
+
attrs << %(owner="#{owner}") if owner
|
|
145
|
+
attrs << %(tpos="#{pkg.tpos}") if pkg.tpos
|
|
146
|
+
attrs << %(ea_localid="#{pkg.package_id}")
|
|
147
|
+
attrs << %(ea_eleType="package")
|
|
148
|
+
"\t\t\t<model #{attrs.join(" ")}/>"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def model_for_object(obj)
|
|
152
|
+
pkg_ref = owner_package_ref(obj)
|
|
153
|
+
attrs = []
|
|
154
|
+
attrs << %(package="#{pkg_ref}") if pkg_ref
|
|
155
|
+
attrs << %(tpos="#{obj.tpos}") if obj.tpos
|
|
156
|
+
attrs << %(ea_localid="#{obj.ea_object_id}")
|
|
157
|
+
attrs << %(ea_eleType="element")
|
|
158
|
+
"\t\t\t<model #{attrs.join(" ")}/>"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def properties_for(documentation, stype:, scope:, is_abstract: nil)
|
|
162
|
+
attrs = []
|
|
163
|
+
attrs << %(documentation="#{escape(documentation)}") unless documentation.nil? || documentation.empty?
|
|
164
|
+
attrs << %(isSpecification="false")
|
|
165
|
+
attrs << %(sType="#{stype}")
|
|
166
|
+
attrs << %(nType="0")
|
|
167
|
+
attrs << %(scope="#{scope}")
|
|
168
|
+
attrs << %(isAbstract="#{is_abstract ? "true" : "false"}")
|
|
169
|
+
"\t\t\t<properties #{attrs.join(" ")}/>"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def style_for_package(_pkg)
|
|
173
|
+
# Packages in t_package have no style column; EA emits a
|
|
174
|
+
# default style block for them.
|
|
175
|
+
default = "BackColor=-1;BorderColor=-1;BorderWidth=-1;" \
|
|
176
|
+
"FontColor=-1;VSwimLanes=1;HSwimLanes=1;BorderStyle=0;"
|
|
177
|
+
%(\t\t\t<style appearance="#{escape(default)}"/>)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def style_for_object(obj)
|
|
181
|
+
appearance = obj.style && obj.style.empty? ? default_object_style : obj.style
|
|
182
|
+
%(\t\t\t<style appearance="#{escape(appearance.to_s)}"/>)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def default_object_style
|
|
186
|
+
"BackColor=-1;BorderColor=-1;BorderWidth=-1;" \
|
|
187
|
+
"FontColor=-1;VSwimLanes=1;HSwimLanes=1;BorderStyle=0;"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# ---- <tags> -----------------------------------------------------
|
|
191
|
+
|
|
192
|
+
# Tags section for one model element. Looks up tagged values
|
|
193
|
+
# whose ElementID matches the given GUID. Returns an empty
|
|
194
|
+
# <tags/> when there are none — matches EA's output shape.
|
|
195
|
+
def tags_block_for(element_guid)
|
|
196
|
+
tags = tagged_values_for(element_guid)
|
|
197
|
+
if tags.empty?
|
|
198
|
+
"\t\t\t<tags/>"
|
|
199
|
+
else
|
|
200
|
+
inner = tags.map { |tv| tag_xml(tv) }.join("\n")
|
|
201
|
+
"\t\t\t<tags>\n#{inner}\n\t\t\t</tags>"
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def tag_xml(tv)
|
|
206
|
+
tag_id = GuidFormat.ea_guid_to_xmi_id(tv.property_id)
|
|
207
|
+
tag_name = tv.tag_name.to_s
|
|
208
|
+
value = tv.parsed_value
|
|
209
|
+
model_element = element_ref_for_tag(tv)
|
|
210
|
+
attrs = [%(xmi:id="#{tag_id}"), %(name="#{escape(tag_name)}")]
|
|
211
|
+
attrs << %(value="#{escape(value)}") unless value.nil? || value.empty?
|
|
212
|
+
attrs << %(modelElement="#{model_element}") if model_element
|
|
213
|
+
"\t\t\t\t<tag #{attrs.join(" ")}/>"
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def tagged_values_for(element_guid)
|
|
217
|
+
return [] unless element_guid
|
|
218
|
+
|
|
219
|
+
@database.tagged_values_for_element(element_guid)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def element_ref_for_tag(tv)
|
|
223
|
+
return nil unless tv.element_id
|
|
224
|
+
|
|
225
|
+
GuidFormat.ea_guid_to_xmi_id(tv.element_id)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# ---- <attributes> block -----------------------------------------
|
|
229
|
+
|
|
230
|
+
# Classifier types that own attributes in their <attributes>
|
|
231
|
+
# extension block. Enumerations use <ownedLiteral> instead;
|
|
232
|
+
# InstanceSpecifications have no attributes.
|
|
233
|
+
ATTRIBUTE_OWNERS = %w[Class Interface DataType PrimitiveType].freeze
|
|
234
|
+
|
|
235
|
+
def attributes_block_for(obj)
|
|
236
|
+
return "" unless ATTRIBUTE_OWNERS.include?(obj.object_type)
|
|
237
|
+
|
|
238
|
+
attrs = @context.attributes_for(obj.ea_object_id)
|
|
239
|
+
return "" if attrs.empty?
|
|
240
|
+
|
|
241
|
+
inner = attrs.map { |attr| attribute_xml(attr) }.join("\n")
|
|
242
|
+
"\t\t\t<attributes>\n#{inner}\n\t\t\t</attributes>"
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def attribute_xml(attr)
|
|
246
|
+
idref = @context.xmi_id_for(attr)
|
|
247
|
+
scope = attribute_scope(attr)
|
|
248
|
+
lines = [
|
|
249
|
+
%(\t\t\t\t<attribute xmi:idref="#{idref}" name="#{escape(attr.name.to_s)}" scope="#{scope}">),
|
|
250
|
+
"\t\t\t\t\t<initial/>",
|
|
251
|
+
documentation_element(attr.notes),
|
|
252
|
+
"\t\t\t\t\t<model ea_localid=\"#{attr.id}\" ea_guid=\"#{escape(attr.ea_guid.to_s)}\"/>",
|
|
253
|
+
attribute_properties(attr),
|
|
254
|
+
"\t\t\t\t\t<coords ordered=\"0\" scale=\"0\"/>",
|
|
255
|
+
"\t\t\t\t\t<containment containment=\"Not Specified\" position=\"#{attr.pos || 0}\"/>",
|
|
256
|
+
"\t\t\t\t\t<stereotype/>",
|
|
257
|
+
bounds_xml(attr),
|
|
258
|
+
"\t\t\t\t\t<options/>",
|
|
259
|
+
"\t\t\t\t\t<style/>",
|
|
260
|
+
%(\t\t\t\t\t<styleex value="#{escape(attr.styleex.to_s)}"/>),
|
|
261
|
+
"\t\t\t\t\t<tags/>",
|
|
262
|
+
xrefs_xml("\t\t\t\t\t", attr.ea_guid, "attribute property"),
|
|
263
|
+
"\t\t\t\t</attribute>"
|
|
264
|
+
]
|
|
265
|
+
lines.join("\n")
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Shares the transformer's pair rule so the extension block and
|
|
269
|
+
# the UML tree can never report different multiplicities.
|
|
270
|
+
def bounds_xml(attr)
|
|
271
|
+
bounds = Cardinality.attribute_bounds(attr.lowerbound, attr.upperbound)
|
|
272
|
+
%(\t\t\t\t\t<bounds lower="#{escape(bounds[:lower])}" upper="#{escape(bounds[:upper])}"/>)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def attribute_scope(attr)
|
|
276
|
+
(attr.scope || "Public").downcase.capitalize
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def attribute_properties(attr)
|
|
280
|
+
attrs = [
|
|
281
|
+
%(type="#{escape(attr.type.to_s)}"),
|
|
282
|
+
%(derived="#{derived_flag(attr)}"),
|
|
283
|
+
%(precision="#{attr.precision || 0}"),
|
|
284
|
+
%(collection="false"),
|
|
285
|
+
%(length="#{attr.length || 0}"),
|
|
286
|
+
%(static="#{attr.isstatic || 0}"),
|
|
287
|
+
%(duplicates="#{attr.allowduplicates || 0}"),
|
|
288
|
+
%(changeability="changeable")
|
|
289
|
+
]
|
|
290
|
+
"\t\t\t\t\t<properties #{attrs.join(" ")}/>"
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def derived_flag(attr)
|
|
294
|
+
attr.derived == "1" ? "1" : "0"
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# ---- <operations> block -----------------------------------------
|
|
298
|
+
|
|
299
|
+
OPERATION_OWNERS = %w[Class Interface DataType PrimitiveType].freeze
|
|
300
|
+
|
|
301
|
+
def operations_block_for(obj)
|
|
302
|
+
return "" unless OPERATION_OWNERS.include?(obj.object_type)
|
|
303
|
+
|
|
304
|
+
ops = @context.operations_for(obj.ea_object_id)
|
|
305
|
+
return "" if ops.empty?
|
|
306
|
+
|
|
307
|
+
inner = ops.map { |op| operation_xml(op) }.join("\n")
|
|
308
|
+
"\t\t\t\t<operations>\n#{inner}\n\t\t\t\t</operations>"
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def operation_xml(op)
|
|
312
|
+
idref = @context.xmi_id_for(op)
|
|
313
|
+
scope = operation_scope(op)
|
|
314
|
+
lines = [
|
|
315
|
+
%(\t\t\t\t<operation xmi:idref="#{idref}" name="#{escape(op.name.to_s)}" scope="#{scope}">),
|
|
316
|
+
%(\t\t\t\t\t<properties position="#{op.pos || 0}"/>),
|
|
317
|
+
"\t\t\t\t\t<stereotype/>",
|
|
318
|
+
"\t\t\t\t\t<model ea_guid=\"#{escape(op.ea_guid.to_s)}\" ea_localid=\"#{op.operationid}\"/>",
|
|
319
|
+
operation_type(op),
|
|
320
|
+
"\t\t\t\t\t<behaviour/>",
|
|
321
|
+
"\t\t\t\t\t<code/>",
|
|
322
|
+
"\t\t\t\t\t<style/>",
|
|
323
|
+
"\t\t\t\t\t<styleex/>",
|
|
324
|
+
*operation_tail(op)
|
|
325
|
+
]
|
|
326
|
+
lines.join("\n")
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def operation_tail(op)
|
|
330
|
+
[documentation_element(op.notes),
|
|
331
|
+
"\t\t\t\t\t<tags/>",
|
|
332
|
+
parameters_block_for(op),
|
|
333
|
+
xrefs_xml("\t\t\t\t\t", op.ea_guid, "operation property"),
|
|
334
|
+
"\t\t\t\t</operation>"]
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def operation_scope(op)
|
|
338
|
+
(op.scope || "Public").downcase.capitalize
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def operation_type(op)
|
|
342
|
+
attrs = [
|
|
343
|
+
%(type="#{escape(op.type.to_s)}"),
|
|
344
|
+
%(const="false"),
|
|
345
|
+
%(static="#{op.isstatic || 0}"),
|
|
346
|
+
%(isAbstract="#{op.abstract || 0}"),
|
|
347
|
+
%(synchronised="0"),
|
|
348
|
+
%(concurrency="#{escape((op.concurrency || "Sequential").to_s)}"),
|
|
349
|
+
%(returnarray="0"),
|
|
350
|
+
%(pure="#{op.pure || 0}"),
|
|
351
|
+
%(isQuery="#{op.pure && op.pure != 0 ? "true" : "false"}")
|
|
352
|
+
]
|
|
353
|
+
"\t\t\t\t\t<type #{attrs.join(" ")}/>"
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def parameters_block_for(op)
|
|
357
|
+
entries = [return_parameter_xml(op), *declared_parameters_xml(op)].compact
|
|
358
|
+
return "" if entries.empty?
|
|
359
|
+
|
|
360
|
+
"\t\t\t\t\t<parameters>\n#{entries.join("\n")}\n\t\t\t\t\t</parameters>"
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def declared_parameters_xml(op)
|
|
364
|
+
@context.params_for_operation(op.operationid)
|
|
365
|
+
.reject(&:return?)
|
|
366
|
+
.map { |param| parameter_xml(param) }
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# The trailing children of a synthesized return parameter —
|
|
370
|
+
# all empty, in EA's order.
|
|
371
|
+
RETURN_PARAMETER_TAIL = ["\t\t\t\t\t\t\t<style/>",
|
|
372
|
+
"\t\t\t\t\t\t\t<styleex/>",
|
|
373
|
+
"\t\t\t\t\t\t\t<documentation/>",
|
|
374
|
+
"\t\t\t\t\t\t\t<tags/>",
|
|
375
|
+
"\t\t\t\t\t\t\t<xrefs/>"].freeze
|
|
376
|
+
|
|
377
|
+
# EA lists the return first, as a synthesized parameter with no
|
|
378
|
+
# t_operationparams row behind it. Both its idref and its
|
|
379
|
+
# ea_guid come from the operation's own id via {GuidFormat}, so
|
|
380
|
+
# the two agree by construction — an untyped operation has no
|
|
381
|
+
# return to describe, and one whose nullable t_operation.ea_guid
|
|
382
|
+
# is blank has nothing to derive the ids from.
|
|
383
|
+
def return_parameter_xml(operation)
|
|
384
|
+
return nil if PrimitiveTypes.normalize_name(operation.type).empty?
|
|
385
|
+
return nil unless @context.identifiable?(operation)
|
|
386
|
+
|
|
387
|
+
return_id = GuidFormat.return_parameter_xmi_id(@context.xmi_id_for(operation))
|
|
388
|
+
[%(\t\t\t\t\t\t<parameter xmi:idref="#{return_id}" visibility="public">),
|
|
389
|
+
%(\t\t\t\t\t\t\t<properties pos="0" type="#{escape(operation.type.to_s)}" const="false" ) +
|
|
390
|
+
%(ea_guid="#{GuidFormat.xmi_id_to_ea_guid(return_id)}"/>),
|
|
391
|
+
*RETURN_PARAMETER_TAIL,
|
|
392
|
+
"\t\t\t\t\t\t</parameter>"].join("\n")
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def parameter_xml(param)
|
|
396
|
+
idref = @context.xmi_id_for(param)
|
|
397
|
+
lines = [
|
|
398
|
+
%(\t\t\t\t\t\t<parameter xmi:idref="#{idref}" visibility="public">),
|
|
399
|
+
%(\t\t\t\t\t\t\t<properties pos="#{param.pos || 0}" type="#{escape(param.type.to_s)}" const="false" ea_guid="#{escape(param.ea_guid.to_s)}"/>),
|
|
400
|
+
"\t\t\t\t\t\t\t<style/>",
|
|
401
|
+
"\t\t\t\t\t\t\t<styleex/>",
|
|
402
|
+
documentation_element(param.notes),
|
|
403
|
+
"\t\t\t\t\t\t\t<tags/>",
|
|
404
|
+
"\t\t\t\t\t\t\t<xrefs/>",
|
|
405
|
+
"\t\t\t\t\t\t</parameter>"
|
|
406
|
+
]
|
|
407
|
+
lines.join("\n")
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# Emits a <documentation value="..."/> element for attribute/
|
|
411
|
+
# operation/parameter sub-blocks. Empty element when there's
|
|
412
|
+
# no note content (matches EA's output for blank notes).
|
|
413
|
+
def documentation_element(note)
|
|
414
|
+
if note.nil? || note.empty?
|
|
415
|
+
"\t\t\t\t\t<documentation/>"
|
|
416
|
+
else
|
|
417
|
+
%(\t\t\t\t\t<documentation value="#{escape(note)}"/>)
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# ---- <connectors> section --------------------------------------
|
|
422
|
+
|
|
423
|
+
def build_connectors_section
|
|
424
|
+
connectors = @database.collections[:connectors] || []
|
|
425
|
+
return "" if connectors.empty?
|
|
426
|
+
|
|
427
|
+
inner = connectors.filter_map { |conn| connector_xml(conn) }.join("\n")
|
|
428
|
+
return "" if inner.empty?
|
|
429
|
+
|
|
430
|
+
"\t\t<connectors>\n#{inner}\n\t\t</connectors>"
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def connector_xml(conn)
|
|
434
|
+
return nil unless conn.ea_guid
|
|
435
|
+
|
|
436
|
+
connector_id = @context.xmi_id_for(conn)
|
|
437
|
+
source_obj = @database.find_object(conn.start_object_id)
|
|
438
|
+
target_obj = @database.find_object(conn.end_object_id)
|
|
439
|
+
return nil unless source_obj && target_obj
|
|
440
|
+
|
|
441
|
+
name_attr = conn.name ? %( name="#{escape(conn.name)}") : ""
|
|
442
|
+
inner = end_xml(conn, source_obj, "source") + end_xml(conn, target_obj, "target") + connector_body_xml(conn)
|
|
443
|
+
%(\t\t<connector xmi:idref="#{connector_id}"#{name_attr}>\n#{inner}\t\t</connector>)
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
END_XREF_TYPE = {
|
|
447
|
+
"source" => "connectorSrcEnd property",
|
|
448
|
+
"target" => "connectorDestEnd property"
|
|
449
|
+
}.freeze
|
|
450
|
+
|
|
451
|
+
def end_xml(conn, obj, kind)
|
|
452
|
+
xmi_id = @context.xmi_id_for(obj)
|
|
453
|
+
lines = [
|
|
454
|
+
"\t\t\t<#{kind} xmi:idref=\"#{xmi_id}\">",
|
|
455
|
+
"\t\t\t\t<model ea_localid=\"#{obj.ea_object_id}\" " \
|
|
456
|
+
"type=\"#{obj.object_type}\" name=\"#{escape(obj.name.to_s)}\"/>",
|
|
457
|
+
"\t\t\t\t<role visibility=\"Public\" targetScope=\"instance\"/>",
|
|
458
|
+
"\t\t\t\t<type aggregation=\"none\" containment=\"Unspecified\"/>",
|
|
459
|
+
"\t\t\t\t<constraints/>",
|
|
460
|
+
"\t\t\t\t<modifiers isOrdered=\"false\" changeable=\"none\" isNavigable=\"false\"/>",
|
|
461
|
+
"\t\t\t\t<style value=\"Union=0;Derived=0;AllowDuplicates=0;Owned=0;Navigable=Unspecified;\"/>",
|
|
462
|
+
"\t\t\t\t<documentation/>",
|
|
463
|
+
xrefs_xml("\t\t\t\t", conn.ea_guid, END_XREF_TYPE.fetch(kind)),
|
|
464
|
+
"\t\t\t\t<tags/>",
|
|
465
|
+
"\t\t\t</#{kind}>"
|
|
466
|
+
]
|
|
467
|
+
"#{lines.join("\n")}\n"
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
# Connector-level body (after source/target): model, properties,
|
|
471
|
+
# documentation, appearance, style, tags. Matches the reference
|
|
472
|
+
# EA output shape.
|
|
473
|
+
def connector_body_xml(conn)
|
|
474
|
+
ea_type = conn.connector_type || "Association"
|
|
475
|
+
lines = [
|
|
476
|
+
"\t\t\t<model ea_localid=\"#{conn.connector_id}\"/>",
|
|
477
|
+
%(\t\t\t<properties ea_type="#{escape(ea_type)}" direction="Unspecified"/>),
|
|
478
|
+
"\t\t\t<modifiers isRoot=\"false\" isLeaf=\"false\"/>",
|
|
479
|
+
"\t\t\t<parameterSubstitutions/>",
|
|
480
|
+
"\t\t\t<documentation/>",
|
|
481
|
+
"\t\t\t<appearance linemode=\"3\" linecolor=\"-1\" linewidth=\"0\" " \
|
|
482
|
+
"seqno=\"0\" headStyle=\"0\" lineStyle=\"0\"/>",
|
|
483
|
+
"\t\t\t<extendedProperties virtualInheritance=\"0\"/>",
|
|
484
|
+
"\t\t\t<style/>",
|
|
485
|
+
xrefs_xml("\t\t\t", conn.ea_guid, "connector property"),
|
|
486
|
+
"\t\t\t<tags/>"
|
|
487
|
+
]
|
|
488
|
+
"#{lines.join("\n")}\n"
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# ---- <xrefs> ----------------------------------------------------
|
|
492
|
+
|
|
493
|
+
# EA renders t_xref rows as a $XREFPROP mini-language string in
|
|
494
|
+
# a single value attribute. Derived from the reference exports:
|
|
495
|
+
# token order $XID $NAM $TYP $VIS [$BEH] $PAR $DES $CLT [$SUP]
|
|
496
|
+
# $ENDXREF; — $BEH only when Behavior is non-blank. A blank
|
|
497
|
+
# Supplier renders as "<none>" except on "connector property"
|
|
498
|
+
# rows, where EA omits the token entirely. "diagram properties"
|
|
499
|
+
# rows are never rendered (reference diagrams keep <xrefs/>).
|
|
500
|
+
def xrefs_xml(indent, ea_guid, type)
|
|
501
|
+
rows = @database.xrefs_for_client(ea_guid.to_s)
|
|
502
|
+
.select { |xref| xref.xref_type == type }
|
|
503
|
+
return "#{indent}<xrefs/>" if rows.empty?
|
|
504
|
+
|
|
505
|
+
value = rows.map { |xref| xref_value(xref) }.join
|
|
506
|
+
%(#{indent}<xrefs value="#{escape(value)}"/>)
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def xref_value(xref)
|
|
510
|
+
head = "$XREFPROP=$XID=#{xref.xref_id}$XID;$NAM=#{xref.name}$NAM;" \
|
|
511
|
+
"$TYP=#{xref.xref_type}$TYP;$VIS=#{xref.visibility}$VIS;"
|
|
512
|
+
beh = xref.behavior.to_s.empty? ? "" : "$BEH=#{xref.behavior}$BEH;"
|
|
513
|
+
tail = "$PAR=#{xref.partition}$PAR;$DES=#{xref.description}$DES;" \
|
|
514
|
+
"$CLT=#{xref.client}$CLT;#{supplier_token(xref)}$ENDXREF;"
|
|
515
|
+
"#{head}#{beh}#{tail}"
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def supplier_token(xref)
|
|
519
|
+
return "$SUP=#{xref.supplier}$SUP;" unless xref.supplier.to_s.empty?
|
|
520
|
+
return "" if xref.xref_type == "connector property"
|
|
521
|
+
|
|
522
|
+
"$SUP=<none>$SUP;"
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# ---- <primitivetypes> section ----------------------------------
|
|
526
|
+
|
|
527
|
+
# EA nests every synthesized EAnone_ PrimitiveType definition
|
|
528
|
+
# under a fixed two-package hierarchy inside the Extension.
|
|
529
|
+
def build_primitivetypes_section
|
|
530
|
+
names = PrimitiveTypes.unresolved_names(@database)
|
|
531
|
+
return "" if names.empty?
|
|
532
|
+
|
|
533
|
+
["\t\t<primitivetypes>",
|
|
534
|
+
primitive_package("\t\t\t", "EAPrimitiveTypesPackage", "EA_PrimitiveTypes_Package"),
|
|
535
|
+
primitive_package("\t\t\t\t", "EAnoneTypesPackage", "EA_none_Types_Package"),
|
|
536
|
+
*names.map { |name| primitive_definition(name) },
|
|
537
|
+
"\t\t\t\t</packagedElement>",
|
|
538
|
+
"\t\t\t</packagedElement>",
|
|
539
|
+
"\t\t</primitivetypes>"].join("\n")
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def primitive_package(indent, id, name)
|
|
543
|
+
%(#{indent}<packagedElement xmi:type="uml:Package" xmi:id="#{id}" name="#{name}">)
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
def primitive_definition(name)
|
|
547
|
+
"\t\t\t\t\t<packagedElement xmi:type=\"uml:PrimitiveType\" " \
|
|
548
|
+
"xmi:id=\"#{escape(PrimitiveTypes.definition_id(name))}\" name=\"#{escape(name)}\"/>"
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
# ---- <diagrams> section ----------------------------------------
|
|
552
|
+
|
|
553
|
+
def build_diagrams_section
|
|
554
|
+
diagrams = @database.collections[:diagrams] || []
|
|
555
|
+
return "" if diagrams.empty?
|
|
556
|
+
|
|
557
|
+
inner = diagrams.filter_map { |dgm| diagram_xml(dgm) }.join("\n")
|
|
558
|
+
return "" if inner.empty?
|
|
559
|
+
|
|
560
|
+
"\t\t<diagrams>\n#{inner}\n\t\t</diagrams>"
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
def diagram_xml(dgm)
|
|
564
|
+
return nil unless dgm.ea_guid
|
|
565
|
+
|
|
566
|
+
diagram_id = @context.xmi_id_for(dgm)
|
|
567
|
+
pkg_guid = package_guid_for(dgm)
|
|
568
|
+
pkg_ref = pkg_guid ? GuidFormat.ea_guid_to_xmi_id(pkg_guid, prefix: "EAPK") : ""
|
|
569
|
+
elements_xml = diagram_elements_xml(dgm)
|
|
570
|
+
lines = [
|
|
571
|
+
"\t\t<diagram xmi:id=\"#{diagram_id}\">",
|
|
572
|
+
"\t\t\t<model package=\"#{pkg_ref}\" localID=\"#{dgm.diagram_id}\" owner=\"#{pkg_ref}\"/>",
|
|
573
|
+
"\t\t\t<properties name=\"#{escape(dgm.name.to_s)}\" type=\"#{escape(dgm.diagram_type.to_s)}\"/>",
|
|
574
|
+
elements_xml,
|
|
575
|
+
"\t\t</diagram>"
|
|
576
|
+
]
|
|
577
|
+
lines.join("\n")
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
# EA emits object placements (t_diagramobjects, ordered by
|
|
581
|
+
# Sequence) followed by connector entries (t_diagramlinks).
|
|
582
|
+
# Derived from examples/exports/basic/model.xml:
|
|
583
|
+
# placement: geometry="Left=..;Top=..;Right=..;Bottom=..;
|
|
584
|
+
# imgL=..;imgT=..;imgR=..;imgB=..;" from the Rect* columns
|
|
585
|
+
# (Top/Bottom sign-flipped; img* at fixed +25/+30 offsets),
|
|
586
|
+
# seqno=Sequence, style=ObjectStyle verbatim.
|
|
587
|
+
# connector: subject is the CONNECTOR's EAID, geometry is the
|
|
588
|
+
# link's Geometry plus "Path=<path>;", style is the link's
|
|
589
|
+
# Style plus "Hidden=<hidden>;".
|
|
590
|
+
def diagram_elements_xml(dgm)
|
|
591
|
+
placements = @database.diagram_objects_for(dgm.diagram_id)
|
|
592
|
+
.sort_by { |placement| placement_order(placement) }
|
|
593
|
+
.filter_map { |placement| object_placement_xml(placement) }
|
|
594
|
+
links = @database.diagram_links_for(dgm.diagram_id)
|
|
595
|
+
.filter_map { |link| connector_entry_xml(link) }
|
|
596
|
+
inner = (placements + links).join("\n")
|
|
597
|
+
return "\t\t\t<elements/>" if inner.empty?
|
|
598
|
+
|
|
599
|
+
"\t\t\t<elements>\n#{inner}\n\t\t\t</elements>"
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
IMG_OFFSET_X = 25
|
|
603
|
+
IMG_OFFSET_Y = 30
|
|
604
|
+
|
|
605
|
+
def object_placement_xml(placement)
|
|
606
|
+
obj = @database.find_object(placement.ea_object_id)
|
|
607
|
+
return nil unless obj
|
|
608
|
+
|
|
609
|
+
"\t\t\t\t<element geometry=\"#{placement_geometry(placement)}\" " \
|
|
610
|
+
"subject=\"#{escape(@context.xmi_id_for(obj).to_s)}\" " \
|
|
611
|
+
"seqno=\"#{placement.sequence.to_i}\" style=\"#{escape(placement.objectstyle.to_s)}\"/>"
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
# Sequence is nullable with DEFAULT 0 — nil sorts as 0, ties
|
|
615
|
+
# broken stably by instance id.
|
|
616
|
+
def placement_order(placement)
|
|
617
|
+
[placement.sequence.to_i, placement.instance_id.to_i]
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
# Rect columns are nullable with DEFAULT 0 — coerce nil to 0.
|
|
621
|
+
def placement_geometry(placement)
|
|
622
|
+
left = placement.rectleft.to_i
|
|
623
|
+
top = -placement.recttop.to_i
|
|
624
|
+
right = placement.rectright.to_i
|
|
625
|
+
bottom = -placement.rectbottom.to_i
|
|
626
|
+
"Left=#{left};Top=#{top};Right=#{right};Bottom=#{bottom};" \
|
|
627
|
+
"imgL=#{left + IMG_OFFSET_X};imgT=#{top + IMG_OFFSET_Y};" \
|
|
628
|
+
"imgR=#{right + IMG_OFFSET_X};imgB=#{bottom + IMG_OFFSET_Y};"
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
def connector_entry_xml(link)
|
|
632
|
+
conn = @database.find_connector(link.connectorid)
|
|
633
|
+
return nil unless conn
|
|
634
|
+
|
|
635
|
+
# EA rewrites the Path waypoints' ";" separators to "$" and
|
|
636
|
+
# terminates with one ";" (reference: Path=165:-230$371:-230$;).
|
|
637
|
+
geometry = "#{style_fragment(link.geometry)}Path=#{link.path.to_s.tr(";", "$")};"
|
|
638
|
+
style = "#{style_fragment(link.style)}Hidden=#{link.hidden};"
|
|
639
|
+
"\t\t\t\t<element geometry=\"#{escape(geometry)}\" " \
|
|
640
|
+
"subject=\"#{escape(@context.xmi_id_for(conn).to_s)}\" style=\"#{escape(style)}\"/>"
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
# Real rows (plateau model) can lack the trailing ";" — append
|
|
644
|
+
# exactly one so concatenated tokens never fuse.
|
|
645
|
+
def style_fragment(text)
|
|
646
|
+
value = text.to_s
|
|
647
|
+
return "" if value.empty?
|
|
648
|
+
|
|
649
|
+
value.end_with?(";") ? value : "#{value};"
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
# ---- helpers ----------------------------------------------------
|
|
653
|
+
|
|
654
|
+
def parent_package_ref(pkg)
|
|
655
|
+
return nil unless pkg.parent_id
|
|
656
|
+
|
|
657
|
+
parent = @database.find_package(pkg.parent_id)
|
|
658
|
+
parent ? GuidFormat.ea_guid_to_xmi_id(parent.ea_guid, prefix: "EAPK") : nil
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
def owner_package_ref(obj)
|
|
662
|
+
return nil unless obj.package_id
|
|
663
|
+
|
|
664
|
+
pkg = @database.find_package(obj.package_id)
|
|
665
|
+
pkg ? GuidFormat.ea_guid_to_xmi_id(pkg.ea_guid, prefix: "EAPK") : nil
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
def package_guid_for(dgm)
|
|
669
|
+
pkg = @database.find_package(dgm.package_id)
|
|
670
|
+
pkg&.ea_guid || dgm.ea_guid
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
# Returns the documentation string for a record. Package notes
|
|
674
|
+
# and object notes come from different columns; both flow
|
|
675
|
+
# through here as the <properties documentation="..."> value.
|
|
676
|
+
def record_documentation(note)
|
|
677
|
+
note.nil? || note.empty? ? nil : note
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
def escape(text)
|
|
681
|
+
XmlEscape.call(text)
|
|
682
|
+
end
|
|
683
|
+
end
|
|
684
|
+
end
|
|
685
|
+
end
|
|
686
|
+
end
|