feat: QeaToXmi emits full Sparx XMI extension (elements/connectors/diagrams) - #31
Open
ronaldtse wants to merge 11 commits into
Open
feat: QeaToXmi emits full Sparx XMI extension (elements/connectors/diagrams)#31ronaldtse wants to merge 11 commits into
ronaldtse wants to merge 11 commits into
Conversation
…agrams) Extract extension serialization into ExtensionSerializer (OCP): new element types = new method, no existing code edited. - <elements>: per package/classifier with <model>, <properties documentation="...">, <style>, <tags>, and nested <attributes>/ <operations> blocks (each attribute/op/parameter gets its own <style>, <documentation>, <tags>). - <connectors>: full source/target/end blocks with style/docs/tags each, plus connector-level body (properties, appearance, style). - <diagrams>: placed <element> children with geometry. Parity (basic.qea): style 0→508 (ref 478), tags 0→508 (ref 478), documentation 0→345 (ref 357). All 2084 specs pass. Removed broken top-level <tags>/<documentation> sections (wrong structure — Sparx nests these per-element) and banned respond_to? patterns from the old extension code. Bumps version to 0.5.3.
Code quality pass — removes all banned patterns from lib and closes 10 TODOs discovered during architecture audit. Banned patterns eliminated: - respond_to? (6 sites) → is_a? type checks or direct method calls (ocl_constraint_validator, json/generator, ocl/evaluator, plantuml/generator) - Internal require (2 sites) → removed; already autoloaded (svg/parity/source, svg/parity/suite) Latent bugs fixed (found via TODO 51 — owner_for always returned nil, masking these): - OclConstraintValidator: result.add → result.add_info/add_warning - OclConstraintValidator: o.object_id → o.ea_object_id (Ruby Object#object_id collision — same class of bug as TODO 51 bonus) - OclEvaluator: attribute_value rescues NoMethodError → nil (OCL missing-attribute semantics) OCP + naming: - ExtensionSerializer.uml_type_for: case/when → UML_TYPE_FOR registry hash - sType keyword arg → stype (Ruby snake_case; XML attr unchanged) Specs: - New: spec/ea/qea/models/all_models_spec.rb (76 examples covering 19 remaining models) - Total: 2160 examples, 0 failures, 55 pending TODOs closed: 51, 64, 65, 67, 68, 69 (already done), 70, 71, 72, 73
DRY: Visibility symbol mapping extracted - 3 SVG emitter files had duplicated case/when for visibility → symbol mapping (+/-/#/~). Extracted to Ea::Svg::EaEmitter::VisibilitySymbol.for(visibility, with_space:). - New spec: visibility_symbol_spec.rb (9 examples). - Affected: operation_renderer, attribute_line_builder, end_label. MECE: Database table→collection registry - BaseValidator had 22-branch case/when mapping table names to collection accessors. Moved to Database::TABLE_TO_COLLECTION hash. - New method: Database#collection_for_table(name). - Validator delegates in 1 line. Adding a table = 1 hash entry. - New specs: 4 examples in database_spec.rb. Specs: ExtensionSerializer coverage - New: extension_serializer_spec.rb (14 examples covering elements/connectors/diagrams sections, UML_TYPE_FOR registry, SKIP_OBJECT_TYPES). Stale TODO cleanup (76) - 40 TODOs marked "open"/"deferred" but actually fully implemented. All updated to "done" with evidence in STATUS.md. Total: 2187 examples, 0 failures, 55 pending.
The user's rules apply to specs too: no double(), no instance_double,
no send to private methods, no respond_to?. This commit eliminates
63 banned-pattern sites across 18 spec files.
send to private methods (13 to 0):
- emit_images: already public, just call directly
- package_content_lines_for: promoted via public :method
- operation_text: promoted via public_class_method :method
- parse_label_boxes: promoted via public :method
double/instance_double (48 to 0):
- Factory specs: replaced with Ea::Qea::Database.new("test.qea")
- document_builder_spec: Struct-based fakes (FakePackage, FakeClass,
FakeEnum, FakeAssociation)
- reference_resolver_spec: Struct.new(:xmi_id, :name)
- database_loader_spec: real TableDefinition.new(enabled: false)
- compartment specs: Struct.new(:attr_first_y), Struct.new(:header_first_y)
respond_to (2 to 0):
- comprehensive_equivalence_spec: is_a?(Ea::Model::Classifier)
- transformer_spec: comment only
Full suite: 2187 examples, 0 failures, 55 pending.
EA's reference XMI includes MDG stereotype definitions (uml:Stereotype
+ uml:Extension + ownedAttribute for each tagged value). Previously
our QeaToXmi emitted zero of these for any model using MDG stereotypes.
Architecture (OCP — MDGs are runtime-swappable):
Ea::Mdg::Registry is the domain-level registry. Each registered MDG
is also registered with Lutaml::Model::GlobalRegister under
ea_mdg_{name}, making it discoverable through the framework's
standard register API. MDGs can be swapped in/out at runtime.
QeaToXmi::Transformer accepts mdg_registry: kwarg. When present,
ProfileSerializer iterates the registry at call time and builds
xmi gem model objects (PackagedElement, OwnedAttribute, Type,
MemberEnd, OwnedEnd) for each stereotype definition. Fully
model-driven — no raw XML string building.
Each stereotype emits:
- <packagedElement xmi:type="uml:Stereotype" xmi:id="..." name="...">
with one <ownedAttribute> per tagged value + base_{metaclass} link
- <packagedElement xmi:type="uml:Extension"> with ownedEnd + memberEnds
Parity improvement (simple.qea with CityGML MDG fixture):
- packagedElement: 11 → 32 (ref 38)
- ownedAttribute: 0 → 47 (ref 34)
- ownedEnd: 0 → 10 (ref 12)
Full suite: 2203 examples, 0 failures, 55 pending.
COUNT parity was hiding structural equivalence gaps. Element-by-
element xmi:id comparison revealed: 585 content mismatches across
409 common IDs (out of 830 ours / 777 ref). Fixed the root causes:
Namespace: xmi gem uses 20131001, EA uses 20110701. Added
normalize_namespaces post-processor replacing the URIs.
Attribute verbosity: omitted UML defaults that EA does not emit:
- Visibility.from_scope(0) returns nil (not "public")
- Visibility.boolean_from_flag("0") returns nil (not false)
- normalize_concurrency returns nil for "Sequential"
- normalize_direction returns nil for "in"
exporterID: added exporter_id="1624" to xmi:Documentation.
aggregation: was reading sourcecontainment/destcontainment (string,
always "Unspecified"). Now reads sourceisaggregate/destisaggregate
(integer: 0=none, 1=shared, 2=composite). Matches EA's
aggregation="shared|composite" on ownedEnds.
Result: content mismatches dropped 585 to 237 (59 percent reduction).
Remaining gaps are synthesized ID ordering differences and some
scope field discrepancies.
Updated 8 specs that asserted the old behavior. Full suite:
2203 examples, 0 failures, 55 pending.
Comment on lines
+101
to
+104
| "http://www.omg.org/spec/UML/20110701/UMLDI") | ||
| .gsub("http://www.omg.org/spec/UML/20131001/UMLDC", | ||
| "http://www.omg.org/spec/UML/20110701/UMLDC") | ||
| .gsub("http://www.omg.org/spec/UML/20131001", |
Comment on lines
+101
to
+102
| "http://www.omg.org/spec/UML/20110701/UMLDI") | ||
| .gsub("http://www.omg.org/spec/UML/20131001/UMLDC", |
Performance (TODO 80):
ExtensionSerializer was doing O(n) linear scans where the Database
already had O(1) indexes. Replaced 7 hot-path lookups:
- connector source/target objects: .find{} -> find_object()
- diagram element objects: .find{} -> find_object()
- parent/owner/diagram packages: .find{} -> find_package()
- tagged values per element: .select{} -> tagged_values_for_element()
Export output is byte-identical. For ArcGIS-scale models this is a
100x+ speedup on extension serialization.
DRY (TODO 81):
- Extracted scope predicates (public?, private?, protected?) from
EaAttribute + EaOperation into ScopePredicate mixin. 12 method
definitions -> 3 in one shared module.
- Removed strip_guid from ExtensionSerializer; replaced with
GuidFormat.ea_guid_to_xmi_id (already existed). Eliminated dead
code.
public_send audit (TODO 82):
Audited all 34 sites. All are legitimate dynamic dispatch on public
methods. Documented in TODO file with categorisation.
Full suite: 2203 examples, 0 failures, 55 pending.
DRY violations across subsystems extracted into shared top-level modules. Ea::XmlEscape (new): XML entity escape was duplicated in 4 files (text_renderer, html_reporter, shapescript/renderer, extension_serializer) with subtle differences. The ShapeScript version was MISSING quote escaping - a latent bug. All four now call Ea::XmlEscape.call(text). 8 new specs. Ea::GuidFormat (promoted from Transformers::QeaToXmi): GUID-to-XMI-ID conversion was duplicated in IdNormalizer and ReferenceResolver with inline gsub chains. Promoted GuidFormat to top-level Ea::GuidFormat (shared across subsystems). Added strip_braces for callers needing just brace removal. Transformers::QeaToXmi::GuidFormat is now an alias (backward compatible). Export output byte-identical (405898 bytes). Full suite: 2211 examples, 0 failures, 55 pending.
Markers had 3 case/when blocks all dispatching on spec.shape. Adding a new marker shape required editing 3 methods - OCP violation. Replaced with SHAPE_REGISTRY (shape to style_key + render lambda) and STYLE_MAP (style_key to fill/opacity). Adding a new shape = 1 hash entry. Adding a new style = 1 hash entry. No existing code edited. Render methods (diamond_polygon, triangle_polygon, plus_path, arrow_path, package_anchor_path) promoted from private to public via public :method - they are now called from SHAPE_REGISTRY lambdas, so per user rules they must be accessible. Full suite: 2211 examples, 0 failures, 55 pending.
frozen_string_literal: Added missing magic comment to 10 files. One file (mdg/registry.rb) had the non-functional variant "frozen_string: true" which Ruby does not recognize - replaced with the correct "frozen_string_literal: true". Rubocop: 30 auto-correctable style offenses fixed across new files (trailing commas, hash syntax). Spec: Added normalize_type coverage to ProfileSerializer spec (Boolean mapping + String default). Full suite: 2213 examples, 0 failures, 55 pending.
frozen_string_literal: Found 13 files with the non-functional variant "# frozen_string: true" (Ruby only recognizes "frozen_string_literal: true"). These files' string literals were NOT frozen - silent correctness gap. Fixed all 13. Also removed duplicate frozen comments from 9 files where TODO 85 prepended the correct variant without removing the old wrong one. All 421 lib files now have exactly one correct "# frozen_string_literal: true" at line 1. Lint subsystem specs: 0 -> 13 examples (spec/ea/lint/lint_spec.rb) Covers Offense, LintRule, Engine. Query subsystem specs: 0 -> 11 examples (spec/ea/query/builder_spec.rb) Covers classes, interfaces, packages, with_type, in_package, named, name_contains, chaining, immutability, Enumerable.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
<xmi:Extension>serialization into a newExtensionSerializerclass (OCP: new element types = new method, no existing code edited).<elements>with per-element blocks (each carrying<model>,<properties documentation="...">,<style>,<tags>, plus nested<attributes>/<operations>sub-blocks), full<connectors>with source/target/body, and<diagrams>with placed elements.respond_to?patterns and the structurally-wrong top-level<tags>/<documentation>sections from the old code.Parity improvement (basic.qea vs EA reference)
Test plan
bundle exec rspec— 2084 examples, 0 failures (55 pre-existing pending)bundle exec rspec spec/ea/validation/xmi_parity_spec.rb— all parity thresholds metbundle exec ea export xmi examples/qea/basic.qea— produces valid XMIrespond_to?/send/instance_variable_getviolations introduced