Skip to content

fix: QEA to XMI export parity - #33

Draft
HassanAkbar wants to merge 13 commits into
mainfrom
fix/issue-32-xmi-parity
Draft

fix: QEA to XMI export parity#33
HassanAkbar wants to merge 13 commits into
mainfrom
fix/issue-32-xmi-parity

Conversation

@HassanAkbar

@HassanAkbar HassanAkbar commented Aug 10, 2026

Copy link
Copy Markdown
Member

Fixes #32.

Gets our QEA→XMI export much closer to EA's own output.

What changed:

  • Reproduces EA's synthesized ID scheme (LI/SL/OE/RT counters).
  • Fixes xrefs and the $XREFPROP mini-language.
  • Fixes diagram geometry, Path encoding, trailing separators.
  • Emits * for unlimited bounds, never -1.
  • Omits association-end bounds when the card is blank, like EA.
  • Adds nested classifiers, dependencies, signals, primitive types.
  • Adds a parity ratchet over 5 of the 7 checked-in models.

Parity distance (only-ours / only-EA / mismatched):

model before after
basic 421/368/237 147/148/100
simple 1/76/0 1/75/0
test 73/363/3 17/305/3

Merge order matters

Companion PR: lutaml/xmi#94. Merge and release that one first.

lutaml-model drops an undeclared attribute without raising. So on xmi
0.6.2 the nested_classifier: we pass vanishes silently. That is 35 of
basic.qea's 65 classes, gone, with no error.

Temporary until xmi 0.7.0 ships:

  • The Gemfile resolves xmi from the companion branch.
  • The gemspec floor sits at >= 0.6.2.
  • After the release: drop the pin, tighten to ~> 0.7.
  • Don't release ea before that lands.

Known gaps

  • EAJava primitive hierarchy for Java-gentype models.
  • uml:PackageImport for Package connectors.
  • Sibling order below nesting level 1. No column explains it yet.
  • Signal receptions still emit as ordinary operations.
  • Parameters carry no <type> child. EA gives all 12 in basic one.
  • Association classes split into uml:Association plus uml:Class.
  • An unresolvable classifier leaves a dangling EAnone_ reference.
    Defining those over-emits, so a pending spec pins it instead.
  • ExtensionEnd writes a <type> child where EA writes type=.
    Blocked: lutaml-model matches attributes by local name, so a plain
    type collides with xmi:type.
  • OwnedAttribute has no is_static, so IsStatic=1 is dropped.
    No fixture hits it and EA's references carry no isStatic.

Pre-existing, worth their own issue

All eight QEA schemas declare ea_guid nullable and the loader filters
nothing, so these are reachable from real data. None is introduced by
this PR — each is on main today. No fixture carries a blank GUID, so
nothing here is exercised by the suite.

  • A null or empty t_connector.ea_guid crashes the export.
    connector_end_xmi_id calls .sub on nil — NoMethodError.
  • An InstanceSpecification with a blank t_object.ea_guid allocates
    tailless SL and OE ids. Null and empty share one allocator key while
    distinct whitespace values get separate counters, so these can emit
    duplicate xmi:id values.
  • A blank t_attribute.ea_guid gives its bounds tailless LI ids. The
    global LI counter keeps them unique, so this one is cosmetic.
  • An object with no GUID emits a packagedElement carrying no xmi:id
    in the model tree, while the extension block skips it entirely.

@HassanAkbar
HassanAkbar marked this pull request as draft August 10, 2026 17:04
@suleman-uzair suleman-uzair changed the title fix: QEA to XMI export parity (issue #32) fix: QEA to XMI export parity Aug 10, 2026
@HassanAkbar
HassanAkbar requested a lite review from Copilot August 11, 2026 06:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens QEA → Sparx XMI export parity by aligning synthesized IDs, visibility handling, xref/$XREFPROP serialization, diagram geometry/path encoding, and emitting additional UML elements (nested classifiers, dependencies, signals, primitive types). It also adds a parity “ratchet” spec to prevent regressions against the checked-in EA reference exports.

Changes:

  • Reworked synthesized ID allocation (LI/SL/OE/RT) and classifier walk/allocation order to better match EA’s counters and nesting conventions.
  • Implemented/expanded Extension serialization: populated <xrefs> via $XREFPROP, added <primitivetypes>, and improved diagram <elements> geometry/style/path output.
  • Added/expanded specs for parity, visibility/textual scopes, run-state operator handling, MDG registry discovery, and CLI export behavior.

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
spec/ea/transformers/qea_to_xmi/visibility_spec.rb Adds coverage for textual scope values and Public suppression.
spec/ea/transformers/qea_to_xmi/transformer_spec.rb Updates expectations for nested classifiers, visibility, synthesized IDs, signals, primitive hrefs, deps, and realizations.
spec/ea/transformers/qea_to_xmi/run_state_spec.rb Updates expectations to keep multi-char operators verbatim.
spec/ea/transformers/qea_to_xmi/parity_spec.rb New parity ratchet over example models + self-parse check.
spec/ea/transformers/qea_to_xmi/id_allocator_spec.rb Refactors allocator specs to assert EA-derived ID shape/counters.
spec/ea/transformers/qea_to_xmi/extension_serializer_spec.rb Adds expectations for xrefs, signals, primitivetypes, diagram element serialization.
spec/ea/transformers/qea_to_xmi/cardinality_spec.rb Updates unlimited upper bound wire form to * and treats stored -1 as unlimited.
spec/ea/qea/models/ea_xref_spec.rb Adds mapping coverage for Visibility/Behavior/Partition.
spec/ea/qea/models/ea_connector_spec.rb Adds UK spelling realization coverage.
spec/ea/mdg/registry_from_paths_spec.rb New tests for registry loading from files/dirs with graceful skipping.
spec/ea/cli/command/export_spec.rb New CLI export specs for --mdg behavior (xmi-only).
lib/ea/transformers/qea_to_xmi/visibility.rb Supports textual scopes with integer fallback.
lib/ea/transformers/qea_to_xmi/transformer.rb Implements EA-like walk/nesting, bounds preallocation, dependency endpoint refs, signals, classifier ref resolution, realization filtering.
lib/ea/transformers/qea_to_xmi/run_state.rb Emits run-state operator verbatim in OpaqueExpression bodies.
lib/ea/transformers/qea_to_xmi/primitive_types.rb New helper to synthesize EA’s primitivetypes extension hierarchy + OMG href mapping.
lib/ea/transformers/qea_to_xmi/id_allocator.rb Redesigns allocator to memoize by (owner,prefix,seed) with global vs per-owner counters and EA-like tail/width rules.
lib/ea/transformers/qea_to_xmi/extension_serializer.rb Populates xrefs via $XREFPROP, adds primitivetypes section, improves diagram element serialization and Path separator handling.
lib/ea/transformers/qea_to_xmi/cardinality.rb Switches unlimited wire form to * and expands unlimited token normalization.
lib/ea/transformers/qea_to_xmi.rb Autoloads PrimitiveTypes.
lib/ea/qea/models/ea_xref.rb Adds visibility/behavior/partition attributes for xref serialization.
lib/ea/qea/models/ea_object.rb Centralizes transformer type mapping (adds Signal) and keeps enum stereotype override.
lib/ea/qea/models/ea_connector.rb Treats both Realization/Realisation as realizations.
lib/ea/mdg/registry.rb Adds from_paths and recursive directory expansion with safe skipping.
lib/ea/cli/command/mdg.rb Uses Registry.from_paths for MDG discovery.
lib/ea/cli/command/export.rb Threads optional MDG registry through xmi exporter only.
lib/ea/cli/app.rb Adds --mdg CLI option (xmi only).
Gemfile Temporarily pins xmi to a GitHub branch for unreleased features.
ea.gemspec Loosens xmi dependency floor to support the temporary pin.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lib/ea/transformers/qea_to_xmi/cardinality.rb Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.

Comment thread lib/ea/transformers/qea_to_xmi/transformer.rb Outdated
Comment thread lib/ea/transformers/qea_to_xmi/transformer.rb Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

lib/ea/transformers/qea_to_xmi/transformer.rb:724

  • build_return_parameter allocates the RT id tail from @context.xmi_id_for(op). When t_operation.ea_guid is nil/blank, xmi_id_for(op) is nil, which produces tailless RT ids and (because the RT counter is per-owner) can also make RT numbering drift across operations. Consider skipping the synthesized return parameter when the operation has no xmi id (matching the extension serializer’s behavior).
            id: @context.id_allocator.allocate(
              prefix: IdAllocator::RETURN_PARAMETER,
              seed: "return-#{op.operationid}",
              owner_id: @context.xmi_id_for(op),
            ),

lib/ea/transformers/qea_to_xmi/transformer.rb:305

  • build_classifier_with_bounds preallocates association-end bounds even when build_classifier returns nil (e.g., object types without a registered builder). That can advance the global LI counter and allocate IDs for elements that will never be emitted, skewing synthesized-ID parity.
          preallocate_attribute_bounds(obj)
          element = build_classifier(obj)
          preallocate_end_bounds(obj)
          element

Comment thread lib/ea/transformers/qea_to_xmi/transformer.rb Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

lib/ea/transformers/qea_to_xmi/transformer.rb:381

  • package_level_relationships uses Set.new/add?, but Set is not required anywhere in lib. This can raise NameError in production usage depending on load order. Switching to a Hash for de-duplication avoids the dependency on set (or add an explicit require "set").
        def package_level_relationships(pkg)
          emitted = Set.new
          relationship_sources_in(pkg).flat_map do |obj|
            @context.connectors_starting_at(obj.ea_object_id).filter_map do |conn|
              key = RELATIONSHIP_AT_PACKAGE_LEVEL[conn.connector_type]
              next nil unless key

lib/ea/transformers/qea_to_xmi/transformer.rb:716

  • Context#xmi_id_for returns nil when t_operation.ea_guid is nil; in that case EA can't derive the RT tail, and exporting a synthesized return parameter will either allocate a tailless RT id or (depending on downstream behavior) produce broken/parity-divergent XML. Since the extension serializer already skips return entries when the operation guid is blank, the UML-tree return parameter should also be skipped when xmi_id_for(op) is nil.
          # Blank-checked the way PrimitiveTypes normalizes names, so a
          # whitespace-only Type cannot synthesize a return here that
          # primitive discovery then treats as absent.
          params << build_return_parameter(op) unless PrimitiveTypes.normalize_name(op.type).empty?
          params

Comment thread lib/ea/transformers/qea_to_xmi/transformer.rb

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated no new comments.

Suppressed comments (2)

lib/ea/transformers/qea_to_xmi/transformer.rb:302

  • Set is used here without an explicit require "set" anywhere in lib/, so loading this transformer in isolation can raise NameError: uninitialized constant Set depending on load order. Consider avoiding Set here (or adding a require at the top of the file).
        def cyclic_ancestry?(obj)
          seen = Set[obj.ea_object_id]
          current = obj
          while (current = nesting_parent_of(current))
            return true unless seen.add?(current.ea_object_id)

lib/ea/transformers/qea_to_xmi/transformer.rb:384

  • Set is used to track emitted connector IDs, but the transformer file does not require set (and there is no require "set" under lib/). This can raise NameError in some load orders. Using a Hash avoids the dependency and keeps the same semantics.
        def package_level_relationships(pkg)
          emitted = Set.new
          relationship_sources_in(pkg).flat_map do |obj|
            @context.connectors_starting_at(obj.ea_object_id).filter_map do |conn|
              key = RELATIONSHIP_AT_PACKAGE_LEVEL[conn.connector_type]
              next nil unless key
              next nil unless emitted.add?(conn.connector_id)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

lib/ea/transformers/qea_to_xmi/transformer.rb:304

  • Set is used here but the stdlib set is not required anywhere in the project, which will raise NameError: uninitialized constant Set when this code path runs.
        def cyclic_ancestry?(obj)
          seen = Set[obj.ea_object_id]
          current = obj
          while (current = nesting_parent_of(current))
            return true unless seen.add?(current.ea_object_id)
          end
          false

Comment thread spec/ea/transformers/qea_to_xmi/parity_spec.rb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

QEA→XMI export does not match EA's output

3 participants