Class: Ea::Transformers::QeaToXmi::Transformer
- Inherits:
-
Object
- Object
- Ea::Transformers::QeaToXmi::Transformer
- Defined in:
- lib/ea/transformers/qea_to_xmi/transformer.rb
Overview
Orchestrates serialization of an Ea::Qea::Database to Sparx XMI.
Walks the package tree starting at root packages, constructing
xmi gem models (Xmi::Uml::UmlModel, Xmi::Uml::PackagedElement,
Xmi::Uml::OwnedAttribute, Xmi::Uml::OwnedEnd, etc.) from each
QEA row, then asks the xmi gem to render them via
to_xml(use_prefix: true) to produce Sparx XMI in the canonical
mixed-prefix style. The serialized output is run through
XmlSanitizer to strip truly-empty elements that the xmi gem's
round-trip-oriented VALUE_MAP emits but Sparx XMI does not.
Element-kind dispatch (Class vs Enumeration vs DataType vs Instance) is registry-driven — see CLASSIFIER_BUILDERS. Adding a new kind = adding one entry to that hash, no method change. Polymorphism for XMI element shape lives in the xmi gem's models (xmi:type discriminator on PackagedElement), not here.
This is the FULL-FIDELITY path — no Lutaml::Uml::Document intermediate. Sparx-specific concepts (multiplicities, tagged values, stereotypes, primitive types, instance specifications, association ends) come straight from the QEA tables.
Phase 2 will extend the xmi gem with visibility / isAbstract / classifier / aggregation attributes that the QEA database contains but the xmi gem's models don't yet declare.
Constant Summary collapse
- MODEL_NAME =
"EA_Model"- EXPORTER =
"Enterprise Architect"- EXPORTER_VERSION =
"6.5"- RELATIONSHIP_AT_PACKAGE_LEVEL =
{ "Association" => :association, "Aggregation" => :association, "Composition" => :association, "Dependency" => :dependency, "Usage" => :dependency, }.freeze
- CLASSIFIER_BUILDERS =
OCP registry: maps EaObject#transformer_type to the builder that constructs the corresponding Xmi::Uml element. To add a new element kind (Signal, Interface, ...), append one entry here —
build_classifierrequires no edit.Builders are lambdas evaluated via
instance_exec, so they run inside the Transformer instance and can call its private helpers withoutsend/public_senddispatch. { class: ->(obj) { build_class(obj) }, enumeration: ->(obj) { build_enumeration(obj) }, data_type: ->(obj) { build_data_type(obj) }, instance: ->(obj) { build_instance(obj) }, }.freeze
Instance Method Summary collapse
- #build_connectors_extension ⇒ Object
- #build_diagrams_extension ⇒ Object
-
#connector_xml(conn) ⇒ Object
Per-connector XML with xmi:idref to the model-level association (when present) plus source/target references.
- #diagram_xml(dgm) ⇒ Object
- #end_xml(obj, kind) ⇒ Object
- #escape(text) ⇒ Object
-
#extension_inner_xml ⇒ String
XML for the inner content of xmi:Extension:
... ,... ,... . - #guid_to_xmi_id(guid) ⇒ Object
-
#initialize(database) ⇒ Transformer
constructor
A new instance of Transformer.
-
#inject_extension_content(xml) ⇒ Object
Injects EA-specific extension content (connectors, diagrams) into the serialized XMI.
- #package_guid_for(dgm) ⇒ Object
-
#serialize(with_extensions: true) ⇒ String
The xmi gem's VALUE_MAP is generation-friendly (
to: { nil: :omitted, ... }), so empty collections and nil-valued attributes are skipped at the source. - #source_end_xml(conn, source_obj) ⇒ Object
- #target_end_xml(conn, target_obj) ⇒ Object
Constructor Details
#initialize(database) ⇒ Transformer
Returns a new instance of Transformer.
61 62 63 64 |
# File 'lib/ea/transformers/qea_to_xmi/transformer.rb', line 61 def initialize(database) @database = database @context = Context.new(database: database) end |
Instance Method Details
#build_connectors_extension ⇒ Object
122 123 124 125 126 127 128 129 130 |
# File 'lib/ea/transformers/qea_to_xmi/transformer.rb', line 122 def build_connectors_extension connectors = @database.collections[:connectors] || [] return "" if connectors.empty? inner = connectors.map { |conn| connector_xml(conn) }.compact.join("\n") return "" if inner.empty? "\t\t<connectors>\n#{inner}\n\t\t</connectors>" end |
#build_diagrams_extension ⇒ Object
132 133 134 135 136 137 138 139 140 |
# File 'lib/ea/transformers/qea_to_xmi/transformer.rb', line 132 def build_diagrams_extension diagrams = @database.collections[:diagrams] || [] return "" if diagrams.empty? inner = diagrams.map { |dgm| diagram_xml(dgm) }.compact.join("\n") return "" if inner.empty? "\t\t<diagrams>\n#{inner}\n\t\t</diagrams>" end |
#connector_xml(conn) ⇒ Object
Per-connector XML with xmi:idref to the model-level association (when present) plus source/target references.
144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/ea/transformers/qea_to_xmi/transformer.rb', line 144 def connector_xml(conn) return nil unless conn.ea_guid connector_id = "EAID_#{guid_to_xmi_id(conn.ea_guid)}" source_obj = @database.collections[:objects]&.find { |o| o.ea_object_id == conn.start_object_id } target_obj = @database.collections[:objects]&.find { |o| o.ea_object_id == conn.end_object_id } return nil unless source_obj && target_obj name_attr = conn.name ? %( name="#{escape(conn.name)}") : "" inner = source_end_xml(conn, source_obj) + target_end_xml(conn, target_obj) %(<connector xmi:idref="#{connector_id}"#{name_attr}>\n#{inner}\t\t</connector>) end |
#diagram_xml(dgm) ⇒ Object
177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/ea/transformers/qea_to_xmi/transformer.rb', line 177 def diagram_xml(dgm) return nil unless dgm.ea_guid diagram_id = "EAID_#{guid_to_xmi_id(dgm.ea_guid)}" pkg_guid = package_guid_for(dgm) pkg_ref = pkg_guid ? "EAPK_#{guid_to_xmi_id(pkg_guid)}" : "" lines = [ "<diagram xmi:id=\"#{diagram_id}\">", "\t\t\t<model package=\"#{pkg_ref}\" localID=\"#{dgm.diagram_id}\" owner=\"#{pkg_ref}\"/>", "\t\t\t<properties name=\"#{escape(dgm.name.to_s)}\" type=\"#{escape(dgm.diagram_type.to_s)}\"/>", "\t\t</diagram>" ] lines.join("\n") end |
#end_xml(obj, kind) ⇒ Object
165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/ea/transformers/qea_to_xmi/transformer.rb', line 165 def end_xml(obj, kind) xmi_id = "EAID_#{guid_to_xmi_id(obj.ea_guid)}" lines = [ "\t\t\t<#{kind} xmi:idref=\"#{xmi_id}\">", "\t\t\t\t<model ea_localid=\"#{obj.ea_object_id}\" " \ "type=\"#{obj.object_type}\" name=\"#{escape(obj.name.to_s)}\"/>", "\t\t\t\t<role visibility=\"Public\" targetScope=\"instance\"/>", "\t\t\t</#{kind}>" ] lines.join("\n") + "\n" end |
#escape(text) ⇒ Object
206 207 208 209 210 211 |
# File 'lib/ea/transformers/qea_to_xmi/transformer.rb', line 206 def escape(text) text.to_s.gsub("&", "&") .gsub("<", "<") .gsub(">", ">") .gsub('"', """) end |
#extension_inner_xml ⇒ String
Returns XML for the inner content of xmi:Extension:
113 114 115 116 117 118 119 120 |
# File 'lib/ea/transformers/qea_to_xmi/transformer.rb', line 113 def extension_inner_xml parts = [] connectors_xml = build_connectors_extension parts << connectors_xml unless connectors_xml.empty? diagrams_xml = build_diagrams_extension parts << diagrams_xml unless diagrams_xml.empty? parts.join("\n") end |
#guid_to_xmi_id(guid) ⇒ Object
197 198 199 200 201 202 203 204 |
# File 'lib/ea/transformers/qea_to_xmi/transformer.rb', line 197 def guid_to_xmi_id(guid) return "" unless guid guid.to_s.gsub(/[{}]/, "") .tr("-", "_") .prepend("EA") # truncated below .sub(/\AEA/, "") end |
#inject_extension_content(xml) ⇒ Object
Injects EA-specific extension content (connectors, diagrams)
into the serialized XMI. The xmi gem's Extension type is
modeled but doesn't declare connectors / diagrams
children. We post-process the serialized XML to insert
them inside the existing <xmi:Extension> element.
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/ea/transformers/qea_to_xmi/transformer.rb', line 94 def inject_extension_content(xml) content = extension_inner_xml return xml if content.empty? # Replace the empty `<xmi:Extension .../>` or the # closing `</xmi:Extension>` with the populated version. if xml.include?("<xmi:Extension") xml.sub(%r{<xmi:Extension ([^>]+)>\s*</xmi:Extension>}, "<xmi:Extension \\1>\n#{content}\n\t</xmi:Extension>") .sub(/<xmi:Extension ([^>]+)\/>/, "<xmi:Extension \\1>\n#{content}\n\t</xmi:Extension>") else xml end end |
#package_guid_for(dgm) ⇒ Object
192 193 194 195 |
# File 'lib/ea/transformers/qea_to_xmi/transformer.rb', line 192 def package_guid_for(dgm) pkg = (@database.collections[:packages] || []).find { |p| p.package_id == dgm.package_id } pkg&.ea_guid || dgm.ea_guid end |
#serialize(with_extensions: true) ⇒ String
The xmi gem's VALUE_MAP is generation-friendly
(to: { nil: :omitted, ... }), so empty collections and
nil-valued attributes are skipped at the source. No
post-processing pass is needed — the prior XmlSanitizer
workaround (TODO 21 §1) has been removed.
Serialize to XMI XML.
Default produces model tree only (parseable by the xmi gem's
Sparx parser for round-trip). Pass with_extensions: true to
also emit EA-specific <connectors> and <diagrams> sections
inside <xmi:Extension>. Those use post-processing string
injection that the xmi gem's parser doesn't model, so the
output is NOT round-trippable when extensions are included.
84 85 86 87 |
# File 'lib/ea/transformers/qea_to_xmi/transformer.rb', line 84 def serialize(with_extensions: true) xml = build_root.to_xml(use_prefix: true) with_extensions ? inject_extension_content(xml) : xml end |
#source_end_xml(conn, source_obj) ⇒ Object
157 158 159 |
# File 'lib/ea/transformers/qea_to_xmi/transformer.rb', line 157 def source_end_xml(conn, source_obj) end_xml(source_obj, "source") end |
#target_end_xml(conn, target_obj) ⇒ Object
161 162 163 |
# File 'lib/ea/transformers/qea_to_xmi/transformer.rb', line 161 def target_end_xml(conn, target_obj) end_xml(target_obj, "target") end |