ea 0.4.0 → 0.5.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/Rakefile +3 -0
- data/TODO.complete/01-t-xref-parsing.md +91 -0
- data/TODO.complete/02-per-label-geometry.md +50 -0
- data/TODO.complete/03-object-properties-constraints.md +57 -0
- data/TODO.complete/04-operation-params.md +37 -0
- data/TODO.complete/05-pdata-flag-matrix.md +52 -0
- data/TODO.complete/06-emf-image-rendering.md +34 -0
- data/TODO.complete/07-auxiliary-tables.md +47 -0
- data/TODO.complete/10-mdg-stereotype-registry.md +40 -0
- data/TODO.complete/11-gml-xsd-generation.md +42 -0
- data/TODO.complete/13-shapescript-interpreter.md +28 -0
- data/TODO.complete/20-cli-diff.md +46 -0
- data/TODO.complete/21-cli-render.md +26 -0
- data/TODO.complete/22-cli-export.md +29 -0
- data/TODO.complete/23-cli-mdg.md +23 -0
- data/TODO.complete/30-headerlines-ocp-refactor.md +72 -0
- data/TODO.complete/31-public-send-cleanup.md +61 -0
- data/TODO.complete/32-spec-coverage.md +33 -0
- data/TODO.complete/33-blocked-parity-gaps.md +41 -0
- data/TODO.complete/36-stereotype-icon-wiring.md +32 -0
- data/TODO.complete/37-cli-lint.md +32 -0
- data/TODO.complete/38-cli-query.md +26 -0
- data/TODO.complete/39-cli-info.md +23 -0
- data/TODO.complete/40-ocl-evaluator.md +31 -0
- data/TODO.complete/41-round-trip-spec.md +18 -0
- data/TODO.complete/42-html-diff-report.md +18 -0
- data/TODO.complete/43-json-schema-export.md +20 -0
- data/TODO.complete/44-remaining-qea-tables.md +40 -0
- data/TODO.complete/45-shapescript-extensions.md +25 -0
- data/TODO.complete/46-xsd-consolidation.md +20 -0
- data/TODO.complete/47-headerlines-legacy-removal.md +20 -0
- data/TODO.complete/STATUS.md +109 -0
- data/TODO.diagrams/01-svg-measurement-harness.md +31 -0
- data/TODO.diagrams/02-polygon-over-rendering.md +29 -0
- data/TODO.diagrams/03-text-over-rendering.md +30 -0
- data/TODO.diagrams/04-rect-under-rendering.md +33 -0
- data/TODO.diagrams/05-canvas-dimension-parity.md +51 -0
- data/TODO.diagrams/06-group-layering-parity.md +39 -0
- data/TODO.diagrams/07-text-content-fidelity.md +65 -0
- data/TODO.diagrams/08-qea-direct-pipeline.md +46 -0
- data/TODO.diagrams/12-connector-labels.md +40 -0
- data/TODO.diagrams/12-per-entity-svg-layer-ordering.md +24 -0
- data/TODO.diagrams/13-marker-polygon-vs-path-dispatch.md +20 -0
- data/TODO.diagrams/13-per-text-type-font-sizes.md +33 -0
- data/TODO.diagrams/14-center-header-text.md +33 -0
- data/TODO.diagrams/14-filter-diagram-frame-elements.md +18 -0
- data/TODO.diagrams/15-deep-dead-code-audit.md +47 -0
- data/TODO.diagrams/15-per-element-font-from-style.md +24 -0
- data/TODO.diagrams/16-canvas-marker-extent.md +30 -0
- data/TODO.diagrams/16-document-remaining-parity-gaps.md +91 -0
- data/TODO.diagrams/17-italic-abstract-classes.md +28 -0
- data/TODO.diagrams/17-remaining-parity-gaps.md +90 -0
- data/TODO.diagrams/18-canvas-from-image-bounds.md +20 -0
- data/TODO.diagrams/18-property-lookup-index.md +42 -0
- data/TODO.diagrams/19-connector-sx-sy-routing.md +27 -0
- data/TODO.diagrams/19-replace-respond-to-with-is-a.md +31 -0
- data/TODO.diagrams/20-split-labels-emitter.md +56 -0
- data/TODO.diagrams/20-visibility-glyph-font-encoding.md +27 -0
- data/TODO.diagrams/21-aggregation-navigability-arrow.md +21 -0
- data/TODO.diagrams/21-auto-generated-legend-block.md +65 -0
- data/TODO.diagrams/22-ghost-element-discriminator.md +57 -0
- data/TODO.diagrams/22-style-based-path-grouping.md +28 -0
- data/TODO.diagrams/23-marker-polygon-deduplication.md +47 -0
- data/TODO.diagrams/23-multiplicity-label-positioning.md +29 -0
- data/TODO.diagrams/24-marker-anchor-structural.md +35 -0
- data/TODO.diagrams/24-track-marker-anchors-structurally.md +66 -0
- data/TODO.diagrams/24-truncate-properties-to-fit-box.md +24 -0
- data/TODO.diagrams/26-phantom-general-parent-rendering.md +59 -0
- data/TODO.diagrams/27-path-grouping-mode-detection.md +43 -0
- data/TODO.diagrams/28-architecture-cleanup-batch.md +47 -0
- data/TODO.diagrams/30-decompose-elements-groups-for.md +52 -0
- data/TODO.diagrams/32-examples-coverage-batch.md +78 -0
- data/TODO.diagrams/36-instance-specification-model.md +42 -0
- data/TODO.diagrams/37-element-icon-decorations.md +41 -0
- data/TODO.diagrams/38-html-note-and-receptions.md +32 -0
- data/TODO.diagrams/39-property-stereotype-discriminator.md +40 -0
- data/TODO.diagrams/40-standalone-1-discriminator.md +38 -0
- data/TODO.diagrams/41-xmi-to-svg-pipeline.md +113 -0
- data/TODO.diagrams/42-close-all-parity-deltas.md +69 -0
- data/TODO.diagrams/43-marker-deduplication-strategy.md +54 -0
- data/TODO.diagrams/44-element-icon-decorations.md +34 -0
- data/TODO.diagrams/45-phantom-parent-class-boxes.md +40 -0
- data/TODO.diagrams/46-user-drawn-legend-swatches.md +44 -0
- data/TODO.diagrams/47-attribute-line-fidelity.md +72 -0
- data/TODO.diagrams/48-spec-coverage-critical-emitter-components.md +45 -0
- data/TODO.diagrams/49-spec-coverage-orchestrators.md +25 -0
- data/TODO.diagrams/50-examples-parity.md +85 -0
- data/TODO.diagrams/51-property-oy-gap-and-remaining-gaps.md +48 -0
- data/TODO.diagrams/52-marker-registration-architecture.md +89 -0
- data/TODO.diagrams/53-remaining-example-gaps.md +86 -0
- data/TODO.diagrams/54-display-name-nested-classes.md +40 -0
- data/TODO.diagrams/55-visibility-icon-discriminator.md +58 -0
- data/TODO.diagrams/56-package-contents-rendering.md +148 -0
- data/TODO.diagrams/57-instance-specification-model.md +100 -0
- data/TODO.diagrams/58-domain-model-note-text.md +67 -0
- data/TODO.diagrams/59-classes-arrow-discriminator.md +57 -0
- data/TODO.diagrams/60-testschema-association-marker.md +40 -0
- data/TODO.diagrams/61-legend-block-rendering.md +55 -0
- data/TODO.diagrams/61-plateau-parity-gaps.md +59 -0
- data/TODO.diagrams/62-ghost-element-rendering.md +56 -0
- data/TODO.diagrams/63-qualified-name-wrap.md +36 -0
- data/TODO.diagrams/64-property-stereotype-labels.md +22 -0
- data/TODO.diagrams/65-path-coordinate-frame.md +62 -0
- data/TODO.diagrams/67-path-coordinate-frame.md +62 -0
- data/TODO.diagrams/68-inherited-attributes.md +65 -0
- data/TODO.diagrams/69-package-anchor-conditional.md +29 -0
- data/TODO.diagrams/70-mdg-technology-support.md +68 -0
- data/TODO.diagrams/71-off-canvas-parent-ghost.md +45 -0
- data/TODO.diagrams/72-basic-attr-divider-paths.md +39 -0
- data/TODO.diagrams/73-plateau-polygon-gap.md +69 -0
- data/TODO.diagrams/74-plateau-rect-gap.md +22 -0
- data/TODO.diagrams/75-plateau-text-gap.md +39 -0
- data/TODO.diagrams/76-basic-poly-path-gaps.md +41 -0
- data/TODO.diagrams/77-basic-text-overrender.md +40 -0
- data/TODO.diagrams/78-headerlines-ocp-refactor.md +65 -0
- data/TODO.diagrams/79-architecture-backlog.md +65 -0
- data/TODO.diagrams/80-basic-triangle-polygons.md +51 -0
- data/TODO.diagrams/81-plateau-path-overrender.md +53 -0
- data/TODO.diagrams/82-appearance-canvas-offset.md +54 -0
- data/TODO.diagrams/83-3d-diagram-subtitle.md +58 -0
- data/TODO.diagrams/84-canvas-offset-mismatches.md +61 -0
- data/TODO.diagrams/85-phantom-property-overrender.md +95 -0
- data/TODO.diagrams/86-foreign-package-rule.md +62 -0
- data/TODO.diagrams/87-internal-tech-encryption.md +80 -0
- data/TODO.diagrams/EXAMPLES_STATUS.md +88 -0
- data/TODO.diagrams/README.md +69 -0
- data/TODO.diagrams/STATUS.md +73 -0
- data/TODO.fixup/01-default-grouped-paths.md +34 -0
- data/TODO.fixup/02-marker-consolidation-per-connector.md +20 -0
- data/TODO.fixup/03-diagram-font-inference.md +28 -0
- data/TODO.fixup/04-extract-bounds-calculator.md +25 -0
- data/TODO.fixup/05-marker-registry-ocp.md +32 -0
- data/TODO.fixup/06-connector-path-bend-routing.md +28 -0
- data/TODO.fixup/07-edge-attachment-formula.md +22 -0
- data/TODO.fixup/08-visual-diff-harness.md +28 -0
- data/TODO.fixup/09-extract-element-compartments.md +34 -0
- data/TODO.fixup/10-canvas-width-overshoot-fix.md +20 -0
- data/TODO.fixup/11-layer-sequencer-extraction.md +18 -0
- data/TODO.fixup/12-path-style-harmony.md +23 -0
- data/TODO.fixup/13-datatype-stereotype-fallback.md +28 -0
- data/TODO.fixup/14-ea-exe-reverse-engineering-notes.md +56 -0
- data/TODO.fixup/15-enumeration-literal-rendering.md +30 -0
- data/TODO.fixup/16-operation-compartment-rendering.md +42 -0
- data/TODO.fixup/17-path-count-calibration.md +27 -0
- data/TODO.fixup/18-polygon-count-calibration.md +32 -0
- data/TODO.fixup/19-rect-count-undershoot-investigation.md +40 -0
- data/TODO.fixup/20-stereotype-color-from-classifier.md +18 -0
- data/TODO.fixup/21-visibility-filter-for-properties.md +24 -0
- data/TODO.fixup/22-multiplicity-display-rule-calibration.md +25 -0
- data/TODO.fixup/23-diagram-frame-title-element.md +31 -0
- data/TODO.fixup/24-note-element-rendering.md +23 -0
- data/TODO.fixup/25-ea-exe-disassembly-findings.md +177 -0
- data/TODO.fixup/26-svg-renderer-architecture.md +78 -0
- data/TODO.fixup/27-svg-element-emitters-disassembly.md +135 -0
- data/TODO.fixup/28-geometry-parser-identification.md +57 -0
- data/TODO.fixup/29-qea-svg-xmi-triple-comparison.md +185 -0
- data/TODO.fixup/30-theme-system-stub.md +46 -0
- data/TODO.fixup/31-theme-value-object-registry.md +17 -0
- data/TODO.fixup/32-styleex-on-diagram-model.md +22 -0
- data/TODO.fixup/33-text-encoding-decimals-rotation.md +29 -0
- data/TODO.fixup/34-per-type-fill-via-theme.md +18 -0
- data/TODO.fixup/35-stroke-width-per-theme.md +7 -0
- data/TODO.fixup/36-qea-rect-coord-normalization.md +24 -0
- data/TODO.fixup/37-package-shape-rendering.md +22 -0
- data/TODO.fixup/38-note-element-rendering.md +15 -0
- data/TODO.fixup/39-styleex-flag-honoring.md +25 -0
- data/TODO.fixup/40-connector-dashed-lines.md +15 -0
- data/TODO.fixup/41-diagram-frame-showborder-detection.md +23 -0
- data/TODO.fixup/42-textrenderer-centralization.md +10 -0
- data/TODO.fixup/43-colorresolver-unification.md +24 -0
- data/TODO.fixup/44-theme-aware-textrenderer.md +17 -0
- data/TODO.fixup/45-theme-aware-fontresolver.md +22 -0
- data/TODO.fixup/46-style-parameterized-by-theme.md +21 -0
- data/TODO.fixup/47-canvas-frame-inset.md +19 -0
- data/TODO.fixup/48-background-rect-format.md +15 -0
- data/TODO.fixup/49-all-samples-integration-test.md +17 -0
- data/TODO.fixup/50-svg-emitter-architecture-docs.md +33 -0
- data/TODO.fixup/51-promote-theme-to-ea-namespace.md +28 -0
- data/TODO.fixup/52-theme-definition-integrated-fills.md +26 -0
- data/TODO.fixup/53-theme-registry-yaml-loading.md +50 -0
- data/TODO.fixup/54-diagram-theme-api.md +50 -0
- data/TODO.fixup/55-update-eaemitter-to-ea-theme.md +23 -0
- data/TODO.fixup/56-theme-yaml-files.md +65 -0
- data/TODO.fixup/57-theme-specs-and-integration.md +27 -0
- data/TODO.fixup/58-ea-entry-point-theme-autoload.md +20 -0
- data/TODO.fixup/59-thread-theme-through-all-renderers.md +29 -0
- data/TODO.fixup/60-cli-theme-option.md +22 -0
- data/TODO.fixup/61-theme-validation.md +28 -0
- data/TODO.fixup/62-theme-builder-dsl.md +31 -0
- data/TODO.fixup/63-theme-integration-rendering-spec.md +24 -0
- data/TODO.fixup/64-theme-writeback-styleex.md +21 -0
- data/TODO.fixup/65-remove-dead-eaemitter-theme-files.md +30 -0
- data/TODO.fixup/66-styleex-writeback.md +22 -0
- data/TODO.fixup/67-delete-dead-code.md +28 -0
- data/TODO.fixup/68-figure-parity-status.md +82 -0
- data/TODO.fixup/69-package-polygon-rendering.md +54 -0
- data/TODO.fixup/70-tagged-values-compartment.md +39 -0
- data/TODO.fixup/71-auto-detect-theme-from-xmi.md +37 -0
- data/TODO.fixup/72-sample-exports-as-regression-specs.md +37 -0
- data/TODO.fixup/73-delete-dead-transformations.md +14 -0
- data/TODO.fixup/74-delete-dead-diagram-renderers.md +18 -0
- data/TODO.fixup/75-visibility-legend-icons.md +23 -0
- data/TODO.fixup/76-plateau-text-undershoot.md +27 -0
- data/TODO.fixup/77-canvas-frame-margins.md +28 -0
- data/TODO.fixup/78-note-element-rendering.md +30 -0
- data/TODO.fixup/79-operation-compartment-rendering.md +27 -0
- data/TODO.fixup/80-tagged-value-stereotype-visibility.md +37 -0
- data/TODO.fixup/81-figure-parity-current-state.md +63 -0
- data/config/qea_schema.yml +356 -0
- data/config/themes/119.yml +98 -0
- data/config/themes/default.yml +14 -0
- data/examples/exports/20251010_current_plateau_v5/UML_EA.DTD +6856 -0
- data/examples/exports/20251010_current_plateau_v5/model.xml +96709 -0
- data/examples/exports/ArcGISWorkspace_template/UML_EA.DTD +6856 -0
- data/examples/exports/ArcGISWorkspace_template/model.xml +1427 -0
- data/examples/exports/UmlModel_template/UML_EA.DTD +6856 -0
- data/examples/exports/UmlModel_template/model.xml +23 -0
- data/examples/exports/basic/UML_EA.DTD +6856 -0
- data/examples/exports/basic/model.xml +8039 -0
- data/examples/exports/simple/UML_EA.DTD +6856 -0
- data/examples/exports/simple/model.xml +585 -0
- data/examples/exports/simple_example/UML_EA.DTD +6856 -0
- data/examples/exports/simple_example/model.xml +23 -0
- data/examples/exports/test/UML_EA.DTD +6856 -0
- data/examples/exports/test/model.xml +1638 -0
- data/examples/qea/basic.qea +0 -0
- data/frontend/README.md +51 -0
- data/frontend/dist/app.iife.js +24 -0
- data/frontend/dist/style.css +1 -0
- data/frontend/index.html +26 -0
- data/frontend/package-lock.json +2359 -0
- data/frontend/package.json +26 -0
- data/frontend/src/App.vue +59 -0
- data/frontend/src/app.ts +8 -0
- data/frontend/src/components/AppHeader.vue +127 -0
- data/frontend/src/components/AppSidebar.vue +119 -0
- data/frontend/src/components/ClassDetails.vue +247 -0
- data/frontend/src/components/DiagramView.vue +79 -0
- data/frontend/src/components/PackageDetails.vue +92 -0
- data/frontend/src/components/PackageTreeNode.vue +64 -0
- data/frontend/src/components/SearchResults.vue +48 -0
- data/frontend/src/components/WelcomeView.vue +87 -0
- data/frontend/src/shims-vue.d.ts +5 -0
- data/frontend/src/stores/dataStore.ts +79 -0
- data/frontend/src/stores/uiStore.ts +167 -0
- data/frontend/src/styles/main.css +1566 -0
- data/frontend/src/types.ts +221 -0
- data/frontend/tests/e2e/__screenshots__/new-dark-mode.png +0 -0
- data/frontend/tests/e2e/__screenshots__/new-package-details.png +0 -0
- data/frontend/tests/e2e/__screenshots__/new-search-modal.png +0 -0
- data/frontend/tests/e2e/__screenshots__/new-sidebar-collapsed.png +0 -0
- data/frontend/tests/e2e/__screenshots__/new-welcome.png +0 -0
- data/frontend/tests/e2e/render.spec.ts +188 -0
- data/frontend/tsconfig.json +20 -0
- data/frontend/vite.config.ts +26 -0
- data/frontend/vitest.config.ts +8 -0
- data/lib/ea/cli/app.rb +57 -2
- data/lib/ea/cli/command/diagrams.rb +37 -28
- data/lib/ea/cli/command/diff.rb +46 -0
- data/lib/ea/cli/command/export.rb +80 -0
- data/lib/ea/cli/command/info.rb +75 -0
- data/lib/ea/cli/command/lint.rb +48 -0
- data/lib/ea/cli/command/mdg.rb +98 -0
- data/lib/ea/cli/command/query.rb +34 -0
- data/lib/ea/cli/command/render.rb +135 -0
- data/lib/ea/cli/command/svg.rb +8 -15
- data/lib/ea/cli/command.rb +7 -0
- data/lib/ea/diagram/display_config.rb +184 -0
- data/lib/ea/diagram.rb +4 -41
- data/lib/ea/diff/change.rb +30 -0
- data/lib/ea/diff/comparator.rb +82 -0
- data/lib/ea/diff/html_reporter.rb +91 -0
- data/lib/ea/diff.rb +15 -0
- data/lib/ea/export/json/generator.rb +42 -0
- data/lib/ea/export/json.rb +12 -0
- data/lib/ea/export/json_schema/generator.rb +77 -0
- data/lib/ea/export/json_schema.rb +12 -0
- data/lib/ea/export/plantuml/generator.rb +73 -0
- data/lib/ea/export/plantuml.rb +12 -0
- data/lib/ea/export/xmi/generator.rb +77 -0
- data/lib/ea/export/xmi.rb +11 -0
- data/lib/ea/export/xsd/class_mapping.rb +65 -0
- data/lib/ea/export/xsd/generator.rb +142 -0
- data/lib/ea/export/xsd/namespace_registry.rb +82 -0
- data/lib/ea/export/xsd.rb +19 -0
- data/lib/ea/export.rb +14 -0
- data/lib/ea/image/emf_renderer.rb +42 -0
- data/lib/ea/image.rb +12 -0
- data/lib/ea/lint/engine.rb +30 -0
- data/lib/ea/lint/lint_rule.rb +38 -0
- data/lib/ea/lint/offense.rb +18 -0
- data/lib/ea/lint/rules/cyclic_generalization.rb +62 -0
- data/lib/ea/lint/rules/duplicate_name.rb +28 -0
- data/lib/ea/lint/rules/missing_stereotype.rb +35 -0
- data/lib/ea/lint/rules/naming_convention.rb +44 -0
- data/lib/ea/lint/rules/orphan_element.rb +28 -0
- data/lib/ea/lint/rules.rb +14 -0
- data/lib/ea/lint.rb +13 -0
- data/lib/ea/mdg/document.rb +120 -0
- data/lib/ea/mdg/loader.rb +118 -0
- data/lib/ea/mdg/registry.rb +98 -0
- data/lib/ea/mdg/stereotype_alias_registry.rb +77 -0
- data/lib/ea/mdg/xml/applies_to.rb +18 -0
- data/lib/ea/mdg/xml/apply.rb +18 -0
- data/lib/ea/mdg/xml/content.rb +18 -0
- data/lib/ea/mdg/xml/documentation.rb +24 -0
- data/lib/ea/mdg/xml/stereotype.rb +28 -0
- data/lib/ea/mdg/xml/stereotypes.rb +18 -0
- data/lib/ea/mdg/xml/tag.rb +28 -0
- data/lib/ea/mdg/xml/tagged_values.rb +18 -0
- data/lib/ea/mdg/xml/technology.rb +28 -0
- data/lib/ea/mdg/xml/uml_profile.rb +22 -0
- data/lib/ea/mdg/xml/uml_profiles.rb +18 -0
- data/lib/ea/mdg/xml.rb +24 -0
- data/lib/ea/mdg.rb +25 -0
- data/lib/ea/model/classifier.rb +4 -0
- data/lib/ea/model/constraint.rb +24 -0
- data/lib/ea/model/diagram.rb +85 -0
- data/lib/ea/model/diagram_connector.rb +34 -0
- data/lib/ea/model/diagram_element.rb +16 -0
- data/lib/ea/model/document.rb +47 -2
- data/lib/ea/model/ghost_label.rb +32 -0
- data/lib/ea/model/instance_specification.rb +34 -0
- data/lib/ea/model/legend.rb +43 -0
- data/lib/ea/model/legend_item.rb +26 -0
- data/lib/ea/model/note.rb +28 -0
- data/lib/ea/model/operation.rb +2 -0
- data/lib/ea/model/property.rb +2 -0
- data/lib/ea/model/relationship.rb +2 -0
- data/lib/ea/model/signal.rb +14 -0
- data/lib/ea/model/slot.rb +19 -0
- data/lib/ea/model.rb +8 -0
- data/lib/ea/ocl/evaluator.rb +94 -0
- data/lib/ea/ocl/nodes.rb +30 -0
- data/lib/ea/ocl/parser.rb +133 -0
- data/lib/ea/ocl/unsupported_error.rb +7 -0
- data/lib/ea/ocl.rb +13 -0
- data/lib/ea/qea/factory/document_builder.rb +24 -10
- data/lib/ea/qea/factory/generalization_builder.rb +9 -11
- data/lib/ea/qea/infrastructure/table_reader.rb +9 -0
- data/lib/ea/qea/models/ea_author.rb +35 -0
- data/lib/ea/qea/models/ea_glossary.rb +34 -0
- data/lib/ea/qea/models/ea_image.rb +53 -0
- data/lib/ea/qea/models/ea_implement.rb +32 -0
- data/lib/ea/qea/models/ea_list.rb +42 -0
- data/lib/ea/qea/models/ea_object_effort.rb +34 -0
- data/lib/ea/qea/models/ea_object_problem.rb +37 -0
- data/lib/ea/qea/models/ea_object_require.rb +34 -0
- data/lib/ea/qea/models/ea_object_resource.rb +34 -0
- data/lib/ea/qea/models/ea_object_risk.rb +34 -0
- data/lib/ea/qea/models/ea_object_scenario.rb +34 -0
- data/lib/ea/qea/models/ea_object_test.rb +35 -0
- data/lib/ea/qea/models/ea_object_trx.rb +34 -0
- data/lib/ea/qea/models/ea_operation.rb +7 -0
- data/lib/ea/qea/models/ea_palette.rb +30 -0
- data/lib/ea/qea/models/ea_palette_item.rb +36 -0
- data/lib/ea/qea/models/ea_phase.rb +40 -0
- data/lib/ea/qea/models/ea_role_constraint.rb +32 -0
- data/lib/ea/qea/models/ea_secrypt.rb +34 -0
- data/lib/ea/qea/models/ea_version.rb +39 -0
- data/lib/ea/qea/models/ea_xref.rb +11 -1
- data/lib/ea/qea/models.rb +19 -0
- data/lib/ea/qea/services/database_loader.rb +22 -1
- data/lib/ea/query/builder.rb +108 -0
- data/lib/ea/query.rb +9 -0
- data/lib/ea/render/image_converter.rb +101 -0
- data/lib/ea/render.rb +10 -0
- data/lib/ea/shapescript/parser.rb +208 -0
- data/lib/ea/shapescript/renderer.rb +74 -0
- data/lib/ea/shapescript/shape.rb +34 -0
- data/lib/ea/shapescript.rb +24 -0
- data/lib/ea/sources/qea/adapter.rb +22 -5
- data/lib/ea/sources/qea/classifier_builder.rb +184 -7
- data/lib/ea/sources/qea/diagram_builder.rb +352 -24
- data/lib/ea/sources/qea/id_normalizer.rb +16 -1
- data/lib/ea/sources/qea/instance_builder.rb +113 -0
- data/lib/ea/sources/qea/legend_builder.rb +137 -0
- data/lib/ea/sources/qea/note_builder.rb +58 -0
- data/lib/ea/sources/qea/object_classifier_map.rb +6 -1
- data/lib/ea/sources/qea/operation_builder.rb +1 -0
- data/lib/ea/sources/qea/package_builder.rb +16 -0
- data/lib/ea/sources/qea/relationship_builder.rb +21 -5
- data/lib/ea/sources/qea/tagged_value_builder.rb +28 -9
- data/lib/ea/sources/qea/xref/legend_definition.rb +35 -0
- data/lib/ea/sources/qea/xref/parser.rb +150 -0
- data/lib/ea/sources/qea/xref/property_definition.rb +29 -0
- data/lib/ea/sources/qea/xref/record.rb +41 -0
- data/lib/ea/sources/qea/xref/stereotype_application.rb +37 -0
- data/lib/ea/sources/qea/xref.rb +27 -0
- data/lib/ea/sources/qea.rb +4 -0
- data/lib/ea/sources/xmi/adapter.rb +80 -2
- data/lib/ea/sources/xmi/classifier_builder.rb +10 -1
- data/lib/ea/sources/xmi/diagram_builder.rb +95 -18
- data/lib/ea/sources/xmi/extension_geometry_parser.rb +19 -2
- data/lib/ea/sources/xmi/extension_style_parser.rb +15 -2
- data/lib/ea/sources/xmi/note_builder.rb +46 -0
- data/lib/ea/sources/xmi/property_builder.rb +9 -0
- data/lib/ea/sources/xmi/stereotype_builder.rb +54 -0
- data/lib/ea/sources/xmi/tag_builder.rb +45 -0
- data/lib/ea/sources/xmi/umldi_keyword_extractor.rb +87 -0
- data/lib/ea/sources/xmi.rb +4 -0
- data/lib/ea/spa/output/sharded_multi_file_strategy.rb +18 -3
- data/lib/ea/spa/output/single_file_strategy.rb +8 -2
- data/lib/ea/spa/output/strategy.rb +34 -0
- data/lib/ea/svg/connector_router.rb +134 -0
- data/lib/ea/svg/ea_emitter/background.rb +2 -3
- data/lib/ea/svg/ea_emitter/bounds_calculator.rb +142 -0
- data/lib/ea/svg/ea_emitter/canvas.rb +56 -33
- data/lib/ea/svg/ea_emitter/color_resolver.rb +56 -0
- data/lib/ea/svg/ea_emitter/compartment/attributes.rb +28 -0
- data/lib/ea/svg/ea_emitter/compartment/constraints.rb +30 -0
- data/lib/ea/svg/ea_emitter/compartment/enum_literals.rb +86 -0
- data/lib/ea/svg/ea_emitter/compartment/header.rb +32 -0
- data/lib/ea/svg/ea_emitter/compartment/header_divider.rb +57 -0
- data/lib/ea/svg/ea_emitter/compartment/instance_slots.rb +96 -0
- data/lib/ea/svg/ea_emitter/compartment/instance_underline.rb +67 -0
- data/lib/ea/svg/ea_emitter/compartment/note_body.rb +52 -0
- data/lib/ea/svg/ea_emitter/compartment/note_text.rb +102 -0
- data/lib/ea/svg/ea_emitter/compartment/operations.rb +38 -0
- data/lib/ea/svg/ea_emitter/compartment/package_contents.rb +72 -0
- data/lib/ea/svg/ea_emitter/compartment/package_from_parent.rb +136 -0
- data/lib/ea/svg/ea_emitter/compartment/shape.rb +82 -0
- data/lib/ea/svg/ea_emitter/compartment/tagged_values.rb +30 -0
- data/lib/ea/svg/ea_emitter/compartment.rb +58 -0
- data/lib/ea/svg/ea_emitter/connectors.rb +46 -12
- data/lib/ea/svg/ea_emitter/diagram_frame.rb +143 -0
- data/lib/ea/svg/ea_emitter/document.rb +15 -24
- data/lib/ea/svg/ea_emitter/element/attribute_line_builder.rb +117 -0
- data/lib/ea/svg/ea_emitter/element/attribute_renderer.rb +119 -0
- data/lib/ea/svg/ea_emitter/element/bcol_decoder.rb +29 -0
- data/lib/ea/svg/ea_emitter/element/compartment_geometry.rb +113 -0
- data/lib/ea/svg/ea_emitter/element/constraint_renderer.rb +53 -0
- data/lib/ea/svg/ea_emitter/element/divider_renderer.rb +17 -0
- data/lib/ea/svg/ea_emitter/element/enumeration_literal_renderer.rb +34 -0
- data/lib/ea/svg/ea_emitter/element/filter.rb +44 -0
- data/lib/ea/svg/ea_emitter/element/hand_draw_shape_renderer.rb +77 -0
- data/lib/ea/svg/ea_emitter/element/header_line_pipeline.rb +43 -0
- data/lib/ea/svg/ea_emitter/element/header_line_provider/instance_spec.rb +27 -0
- data/lib/ea/svg/ea_emitter/element/header_line_provider/name.rb +76 -0
- data/lib/ea/svg/ea_emitter/element/header_line_provider/parent_ghost.rb +25 -0
- data/lib/ea/svg/ea_emitter/element/header_line_provider/stereotype_label.rb +68 -0
- data/lib/ea/svg/ea_emitter/element/header_line_provider.rb +19 -0
- data/lib/ea/svg/ea_emitter/element/header_lines.rb +27 -0
- data/lib/ea/svg/ea_emitter/element/header_renderer.rb +48 -0
- data/lib/ea/svg/ea_emitter/element/icon_renderer.rb +109 -0
- data/lib/ea/svg/ea_emitter/element/legend_renderer.rb +177 -0
- data/lib/ea/svg/ea_emitter/element/note_shape_renderer.rb +112 -0
- data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +100 -0
- data/lib/ea/svg/ea_emitter/element/package_shape_renderer.rb +181 -0
- data/lib/ea/svg/ea_emitter/element/row_icon_renderer.rb +156 -0
- data/lib/ea/svg/ea_emitter/element/shape_renderer.rb +17 -0
- data/lib/ea/svg/ea_emitter/element/stereotype_icon_renderer.rb +82 -0
- data/lib/ea/svg/ea_emitter/element/tagged_value_renderer.rb +50 -0
- data/lib/ea/svg/ea_emitter/element/text_escape.rb +20 -0
- data/lib/ea/svg/ea_emitter/element.rb +37 -0
- data/lib/ea/svg/ea_emitter/elements.rb +283 -95
- data/lib/ea/svg/ea_emitter/font_resolver.rb +111 -0
- data/lib/ea/svg/ea_emitter/ghost_labels.rb +66 -0
- data/lib/ea/svg/ea_emitter/label/end_label.rb +266 -0
- data/lib/ea/svg/ea_emitter/label/midpoint_label.rb +101 -0
- data/lib/ea/svg/ea_emitter/label/registry.rb +50 -0
- data/lib/ea/svg/ea_emitter/label.rb +23 -0
- data/lib/ea/svg/ea_emitter/labels.rb +93 -27
- data/lib/ea/svg/ea_emitter/layer.rb +20 -0
- data/lib/ea/svg/ea_emitter/layer_sequencer.rb +93 -0
- data/lib/ea/svg/ea_emitter/marker/arrow_path.rb +52 -0
- data/lib/ea/svg/ea_emitter/marker/diamond.rb +57 -0
- data/lib/ea/svg/ea_emitter/marker/kind.rb +35 -0
- data/lib/ea/svg/ea_emitter/marker/open_triangle.rb +27 -0
- data/lib/ea/svg/ea_emitter/marker/package_import.rb +39 -0
- data/lib/ea/svg/ea_emitter/marker/plus.rb +40 -0
- data/lib/ea/svg/ea_emitter/marker/registry.rb +42 -0
- data/lib/ea/svg/ea_emitter/marker.rb +37 -0
- data/lib/ea/svg/ea_emitter/markers.rb +351 -64
- data/lib/ea/svg/ea_emitter/render_context.rb +52 -0
- data/lib/ea/svg/ea_emitter/style.rb +15 -0
- data/lib/ea/svg/ea_emitter/text_renderer.rb +92 -0
- data/lib/ea/svg/ea_emitter.rb +33 -11
- data/lib/ea/svg/parity/checker.rb +105 -0
- data/lib/ea/svg/parity/source.rb +59 -0
- data/lib/ea/svg/parity/suite.rb +113 -0
- data/lib/ea/svg/parity.rb +14 -0
- data/lib/ea/svg.rb +2 -15
- data/lib/ea/theme/definition.rb +176 -0
- data/lib/ea/theme/loader.rb +125 -0
- data/lib/ea/theme/registry.rb +96 -0
- data/lib/ea/theme.rb +37 -0
- data/lib/ea/transformations.rb +5 -88
- data/lib/ea/version.rb +4 -1
- data/lib/ea.rb +14 -4
- data/lib/tasks/svg_bench.rake +87 -0
- data/lutaml-uml-0.5.1.gem +0 -0
- data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes-list.png +0 -0
- data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes.png +0 -0
- data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-2-gradients.png +0 -0
- data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-3-colors.png +0 -0
- data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-4-appearance-fonts.png +0 -0
- data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-4-appearance.png +0 -0
- data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-5-behavior.png +0 -0
- data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-6-sequence.png +0 -0
- data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram.png +0 -0
- data/reference-docs/enterprise-architect-appearance-dialogs/appearance-2-objects.png +0 -0
- data/reference-docs/enterprise-architect-appearance-dialogs/appearance-3-links.png +0 -0
- metadata +449 -26
- data/lib/ea/diagram/configuration.rb +0 -379
- data/lib/ea/diagram/element_renderers/base_renderer.rb +0 -77
- data/lib/ea/diagram/element_renderers/class_renderer.rb +0 -323
- data/lib/ea/diagram/element_renderers/connector_renderer.rb +0 -41
- data/lib/ea/diagram/element_renderers/package_renderer.rb +0 -61
- data/lib/ea/diagram/element_renderers.rb +0 -43
- data/lib/ea/diagram/extractor.rb +0 -566
- data/lib/ea/diagram/layout_engine.rb +0 -170
- data/lib/ea/diagram/path_builder.rb +0 -202
- data/lib/ea/diagram/style_parser.rb +0 -42
- data/lib/ea/diagram/style_resolver.rb +0 -291
- data/lib/ea/diagram/svg_renderer.rb +0 -274
- data/lib/ea/diagram/util.rb +0 -73
- data/lib/ea/svg/bounds_calculator.rb +0 -74
- data/lib/ea/svg/connector_path.rb +0 -138
- data/lib/ea/svg/element_box.rb +0 -256
- data/lib/ea/svg/renderer.rb +0 -77
- data/lib/ea/svg/style_resolver.rb +0 -72
- data/lib/ea/transformations/configuration.rb +0 -333
- data/lib/ea/transformations/format_registry.rb +0 -366
- data/lib/ea/transformations/parsers/base_parser.rb +0 -482
- data/lib/ea/transformations/parsers/qea_parser.rb +0 -401
- data/lib/ea/transformations/parsers/xmi_parser.rb +0 -243
- data/lib/ea/transformations/transformation_engine.rb +0 -390
|
@@ -8,10 +8,11 @@ module Ea
|
|
|
8
8
|
# ObjectClassifierMap for dispatch (OCP — new classifier
|
|
9
9
|
# kinds need no edit here).
|
|
10
10
|
class ClassifierBuilder
|
|
11
|
-
attr_reader :database
|
|
11
|
+
attr_reader :database, :mdg_registry
|
|
12
12
|
|
|
13
|
-
def initialize(database)
|
|
13
|
+
def initialize(database, mdg_registry: nil)
|
|
14
14
|
@database = database
|
|
15
|
+
@mdg_registry = mdg_registry
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def build_all
|
|
@@ -23,12 +24,53 @@ module Ea
|
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def build_one(object)
|
|
26
|
-
klass =
|
|
27
|
+
klass = classifier_class_for(object)
|
|
27
28
|
args = common_args(object)
|
|
28
29
|
args.merge!(enumeration_args(object)) if klass == Ea::Model::Enumeration
|
|
30
|
+
args.delete(:properties) if klass == Ea::Model::Enumeration
|
|
29
31
|
klass.new(**args)
|
|
30
32
|
end
|
|
31
33
|
|
|
34
|
+
# EA's `t_object.Object_Type` selects the concrete classifier
|
|
35
|
+
# subclass, but a Class with the "enumeration" stereotype is
|
|
36
|
+
# rendered as an Enumeration (literals compartment, no
|
|
37
|
+
# visibility marker). Promote the model class when the
|
|
38
|
+
# stereotype says so.
|
|
39
|
+
def classifier_class_for(object)
|
|
40
|
+
base = ObjectClassifierMap.class_for(object.object_type)
|
|
41
|
+
return base unless base == Ea::Model::Klass
|
|
42
|
+
return Ea::Model::Enumeration if enumeration_stereotype?(object)
|
|
43
|
+
|
|
44
|
+
base
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def enumeration_stereotype?(object)
|
|
48
|
+
stereotype = object.stereotype.to_s
|
|
49
|
+
!stereotype.empty? && stereotype.downcase == "enumeration"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Resolves the classifier's full attribute list. Combines:
|
|
53
|
+
# 1. Own properties from t_attribute
|
|
54
|
+
# 2. Inherited properties from MDG-defined ancestor classes,
|
|
55
|
+
# when an mdg_registry is provided AND the classifier's
|
|
56
|
+
# applied stereotype maps to an MDG class.
|
|
57
|
+
#
|
|
58
|
+
# Public so integration tests can drive the merge directly
|
|
59
|
+
# without stubbing the entire build_one pipeline.
|
|
60
|
+
#
|
|
61
|
+
# When no mdg_registry is wired in, this returns just the
|
|
62
|
+
# own properties — preserving prior behavior.
|
|
63
|
+
def properties_for(object)
|
|
64
|
+
own = PropertyBuilder.new(database).build_all_for(object)
|
|
65
|
+
return own unless mdg_registry
|
|
66
|
+
|
|
67
|
+
inherited = mdg_inherited_properties_for(object)
|
|
68
|
+
return own if inherited.empty?
|
|
69
|
+
|
|
70
|
+
own_names = own.to_set { |p| p.name.to_s }
|
|
71
|
+
own + inherited.reject { |p| own_names.include?(p.name.to_s) }
|
|
72
|
+
end
|
|
73
|
+
|
|
32
74
|
private
|
|
33
75
|
|
|
34
76
|
def each_classifier_object
|
|
@@ -49,18 +91,123 @@ module Ea
|
|
|
49
91
|
id: IdNormalizer.from_guid(object.ea_guid),
|
|
50
92
|
name: object.name,
|
|
51
93
|
package_id: package_id_for(object),
|
|
94
|
+
package_name: package_name_for(object),
|
|
52
95
|
qualified_name: qualified_name_for(object),
|
|
53
96
|
is_abstract: abstract?(object),
|
|
54
97
|
visibility: visibility_from_scope(object.scope),
|
|
55
|
-
properties:
|
|
98
|
+
properties: properties_for(object),
|
|
56
99
|
operations: OperationBuilder.new(database).build_all_for(object),
|
|
57
100
|
stereotype_refs: stereotype_refs_for(object),
|
|
58
|
-
tagged_values: TaggedValueBuilder.new(database)
|
|
101
|
+
tagged_values: TaggedValueBuilder.new(database)
|
|
102
|
+
.for_object(object.ea_guid,
|
|
103
|
+
ea_object_id: object.ea_object_id),
|
|
104
|
+
constraints: constraints_for(object),
|
|
59
105
|
annotations: AnnotationBuilder.from_note(object.note, object.ea_guid,
|
|
60
106
|
kind: "documentation")
|
|
61
107
|
}
|
|
62
108
|
end
|
|
63
109
|
|
|
110
|
+
# Looks up the classifier's applied stereotype's MDG class
|
|
111
|
+
# via the registry and returns its inherited properties.
|
|
112
|
+
# Returns [] when no stereotype or no matching MDG class.
|
|
113
|
+
def mdg_inherited_properties_for(object)
|
|
114
|
+
stereotype = stereotype_target_name(object)
|
|
115
|
+
return [] if stereotype.nil? || stereotype.empty?
|
|
116
|
+
|
|
117
|
+
classifier = mdg_registry.find_classifier_by_name(stereotype)
|
|
118
|
+
return [] unless classifier
|
|
119
|
+
|
|
120
|
+
mdg_registry.inherited_properties_for(classifier.id).map do |entry|
|
|
121
|
+
build_property_from_mdg_entry(entry)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# EA's stereotype field can carry either the simple name
|
|
126
|
+
# ("Type") or the FQName ("GML::Type"). We extract the
|
|
127
|
+
# class name from either form for MDG lookup.
|
|
128
|
+
def stereotype_target_name(object)
|
|
129
|
+
raw = object.stereotype.to_s
|
|
130
|
+
return nil if raw.empty?
|
|
131
|
+
|
|
132
|
+
# FQName format: "MDGName::ClassName" — take the part
|
|
133
|
+
# after the last "::". EA stores the simple form more
|
|
134
|
+
# often than the FQName.
|
|
135
|
+
raw.split("::").last
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# EA renders association SourceRole names as virtual
|
|
139
|
+
# "property" attributes on the SOURCE classifier. When a
|
|
140
|
+
# connector has SourceRole="lod1ImplicitRepresentation",
|
|
141
|
+
# EA shows "+lod1ImplicitRepresentation" inside the source
|
|
142
|
+
# element's attribute compartment with «property»
|
|
143
|
+
# stereotype.
|
|
144
|
+
def association_role_properties_for(object)
|
|
145
|
+
connectors = database.collections[:connectors] || []
|
|
146
|
+
connectors.filter_map do |conn|
|
|
147
|
+
next nil unless conn.start_object_id == object.ea_object_id
|
|
148
|
+
|
|
149
|
+
role = conn.sourcerole.to_s
|
|
150
|
+
next nil if role.empty?
|
|
151
|
+
|
|
152
|
+
build_assoc_property(conn, role, object)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def build_assoc_property(conn, role, owner_object)
|
|
157
|
+
target = database.find_object(conn.end_object_id) if conn.end_object_id&.to_i&.positive?
|
|
158
|
+
Ea::Model::Property.new(
|
|
159
|
+
id: IdNormalizer.from_guid(conn.ea_guid),
|
|
160
|
+
name: role,
|
|
161
|
+
owner_id: IdNormalizer.from_guid(owner_object.ea_guid),
|
|
162
|
+
type_name: target&.name,
|
|
163
|
+
qualified_name: "#{owner_object.name}::#{role}",
|
|
164
|
+
multiplicity_lower: parse_cardinality(conn.sourcecard, :lower),
|
|
165
|
+
multiplicity_upper: parse_cardinality(conn.sourcecard, :upper),
|
|
166
|
+
visibility: "public",
|
|
167
|
+
aggregation: "none",
|
|
168
|
+
stereotype_refs: ["property"],
|
|
169
|
+
tagged_values: [],
|
|
170
|
+
annotations: []
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def parse_cardinality(card_str, bound)
|
|
175
|
+
return nil if card_str.nil? || card_str.to_s.empty?
|
|
176
|
+
|
|
177
|
+
s = card_str.to_s
|
|
178
|
+
parts = s.split("..")
|
|
179
|
+
val = bound == :lower ? parts.first : (parts.size > 1 ? parts.last : parts.first)
|
|
180
|
+
val = val.to_s.strip
|
|
181
|
+
return -1 if val == "*"
|
|
182
|
+
|
|
183
|
+
Integer(val)
|
|
184
|
+
rescue ArgumentError
|
|
185
|
+
nil
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def build_property_from_mdg_entry(entry)
|
|
189
|
+
Ea::Model::Property.new(
|
|
190
|
+
id: "mdg:#{entry.name}",
|
|
191
|
+
name: entry.name,
|
|
192
|
+
visibility: entry.visibility || "public",
|
|
193
|
+
multiplicity_lower: entry.multiplicity_lower,
|
|
194
|
+
multiplicity_upper: entry.multiplicity_upper
|
|
195
|
+
)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def constraints_for(object)
|
|
199
|
+
rows = database.constraints_for_object(object.ea_object_id) || []
|
|
200
|
+
rows.map do |row|
|
|
201
|
+
Ea::Model::Constraint.new(
|
|
202
|
+
id: IdNormalizer.from_guid("{#{row.constraint_id}}"),
|
|
203
|
+
name: row.constraint.to_s,
|
|
204
|
+
kind: row.constraint_type.to_s,
|
|
205
|
+
body: row.notes.to_s,
|
|
206
|
+
status: row.status.to_s
|
|
207
|
+
)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
64
211
|
def enumeration_args(object)
|
|
65
212
|
literals = enumeration_literals_for(object)
|
|
66
213
|
{ literals: literals }
|
|
@@ -89,10 +236,40 @@ module Ea
|
|
|
89
236
|
IdNormalizer.from_guid(pkg.ea_guid)
|
|
90
237
|
end
|
|
91
238
|
|
|
239
|
+
def package_name_for(object)
|
|
240
|
+
pkg = database.find_package(object.package_id)
|
|
241
|
+
pkg&.name
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# EA renders classifier names with a qualifier prefix using
|
|
245
|
+
# "::" — package prefix for top-level classes, parent-class
|
|
246
|
+
# prefix for nested classes. The QEA t_object.Name may
|
|
247
|
+
# already include a ":" prefix (e.g. "gml:CodeType"); use
|
|
248
|
+
# it verbatim.
|
|
92
249
|
def qualified_name_for(object)
|
|
250
|
+
name = object.name.to_s
|
|
251
|
+
return name if name.include?(":")
|
|
252
|
+
return name if name.empty?
|
|
253
|
+
|
|
254
|
+
parent_name = nested_parent_name(object)
|
|
255
|
+
return "#{parent_name}::#{name}" if parent_name
|
|
256
|
+
|
|
93
257
|
pkg = database.find_package(object.package_id)
|
|
94
|
-
|
|
95
|
-
|
|
258
|
+
pkg_name = pkg&.name
|
|
259
|
+
pkg_name ? "#{pkg_name}::#{name}" : name
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# When t_object.ParentID points at a Class (not a Package),
|
|
263
|
+
# the object is a nested classifier. EA qualifies its name
|
|
264
|
+
# with the parent class's name, not the package name.
|
|
265
|
+
def nested_parent_name(object)
|
|
266
|
+
parent_id_value = object.parentid
|
|
267
|
+
return nil unless parent_id_value && parent_id_value.to_i.positive?
|
|
268
|
+
|
|
269
|
+
parent = database.find_object(parent_id_value.to_i)
|
|
270
|
+
return nil unless parent&.object_type == "Class"
|
|
271
|
+
|
|
272
|
+
parent.name.to_s
|
|
96
273
|
end
|
|
97
274
|
|
|
98
275
|
def abstract?(object)
|
|
@@ -26,6 +26,12 @@ module Ea
|
|
|
26
26
|
package_id: package_id_for(diagram_row),
|
|
27
27
|
diagram_type: diagram_row.diagram_type,
|
|
28
28
|
bounds: bounds_for(diagram_row),
|
|
29
|
+
style: diagram_row.pdata,
|
|
30
|
+
style_ex: diagram_row.styleex,
|
|
31
|
+
show_package_contents: package_contents_enabled?(diagram_row),
|
|
32
|
+
hand_draw: hand_draw_enabled?(diagram_row),
|
|
33
|
+
show_notes: show_notes_enabled?(diagram_row),
|
|
34
|
+
show_parents: show_parents_enabled?(diagram_row),
|
|
29
35
|
elements: build_elements(diagram_row),
|
|
30
36
|
connectors: build_connectors(diagram_row),
|
|
31
37
|
annotations: AnnotationBuilder.from_note(diagram_row.notes,
|
|
@@ -34,6 +40,42 @@ module Ea
|
|
|
34
40
|
)
|
|
35
41
|
end
|
|
36
42
|
|
|
43
|
+
# EA encodes "show package contents" as a non-zero integer in
|
|
44
|
+
# t_diagram.ShowPackageContents (1 in basic.qea, 255 in
|
|
45
|
+
# test.qea). When true, the package body lists its child
|
|
46
|
+
# classifiers/sub-packages as "+ Name" rows.
|
|
47
|
+
def package_contents_enabled?(diagram_row)
|
|
48
|
+
return false if diagram_row.showpackagecontents.nil?
|
|
49
|
+
|
|
50
|
+
diagram_row.showpackagecontents.to_i.nonzero?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# HandDraw=1 in t_diagram.StyleEx triggers EA's wavy
|
|
54
|
+
# hand-drawn border style for every element on the diagram.
|
|
55
|
+
def hand_draw_enabled?(diagram_row)
|
|
56
|
+
style_ex = diagram_row.styleex.to_s
|
|
57
|
+
match = style_ex.match(/HandDraw=(\d)/)
|
|
58
|
+
match && match[1] == "1"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# ShowNotes=1 in t_diagram.StyleEx renders the classifier's
|
|
62
|
+
# documentation note as wrapped text in the element body.
|
|
63
|
+
def show_notes_enabled?(diagram_row)
|
|
64
|
+
style_ex = diagram_row.styleex.to_s
|
|
65
|
+
match = style_ex.match(/ShowNotes=(\d)/)
|
|
66
|
+
match && match[1] == "1"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# HideParents=1 in t_diagram.PDATA suppresses EA's "off-canvas
|
|
70
|
+
# parent class" ghost line — the italic parent name that EA
|
|
71
|
+
# normally prepends to a placed element's header when its
|
|
72
|
+
# generalization parent is not placed on the diagram.
|
|
73
|
+
def show_parents_enabled?(diagram_row)
|
|
74
|
+
pdata = diagram_row.pdata.to_s
|
|
75
|
+
match = pdata.match(/HideParents=(\d)/)
|
|
76
|
+
match ? match[1] != "1" : true
|
|
77
|
+
end
|
|
78
|
+
|
|
37
79
|
private
|
|
38
80
|
|
|
39
81
|
def build_elements(diagram_row)
|
|
@@ -42,41 +84,245 @@ module Ea
|
|
|
42
84
|
end
|
|
43
85
|
|
|
44
86
|
def build_element(obj_row, diagram_row)
|
|
87
|
+
style_hash = DiagramStyleParser.parse(obj_row.objectstyle)
|
|
45
88
|
Ea::Model::DiagramElement.new(
|
|
46
89
|
id: IdNormalizer.synthetic("de", diagram_row.diagram_id,
|
|
47
90
|
obj_row.instance_id),
|
|
48
91
|
diagram_id: IdNormalizer.from_guid(diagram_row.ea_guid),
|
|
49
92
|
model_element_ref: ref_for_object(obj_row),
|
|
50
93
|
bounds: bounds_from_rect(obj_row),
|
|
51
|
-
style:
|
|
94
|
+
style: style_hash,
|
|
95
|
+
font_family: style_hash[:font],
|
|
96
|
+
font_bold: truthy?(style_hash[:bold]),
|
|
97
|
+
font_italic: truthy?(style_hash[:italic]),
|
|
98
|
+
show_tagged_values: truthy?(style_hash[:tag])
|
|
52
99
|
)
|
|
53
100
|
end
|
|
54
101
|
|
|
102
|
+
def truthy?(raw)
|
|
103
|
+
raw == "1" || raw == "-1"
|
|
104
|
+
end
|
|
105
|
+
|
|
55
106
|
def build_connectors(diagram_row)
|
|
56
107
|
links = database.diagram_links_for(diagram_row.diagram_id) || []
|
|
57
|
-
links.map { |link_row| build_connector(link_row, diagram_row) }
|
|
108
|
+
explicit = links.map { |link_row| build_connector(link_row, diagram_row) }
|
|
109
|
+
phantom = phantom_connectors(diagram_row, explicit)
|
|
110
|
+
explicit + phantom
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# EA renders PHANTOM CONNECTORS for associations that exist
|
|
114
|
+
# in t_connector between two elements BOTH placed on the
|
|
115
|
+
# current diagram, even when no t_diagramlinks entry exists
|
|
116
|
+
# for that connector on this diagram. The phantom connector
|
|
117
|
+
# renders as a straight line between the two element edges,
|
|
118
|
+
# with SourceRole labels and appropriate markers.
|
|
119
|
+
#
|
|
120
|
+
# Only Association and Aggregation types produce phantom
|
|
121
|
+
# connectors. Generalization, Dependency, and Package types
|
|
122
|
+
# do NOT get phantom rendering.
|
|
123
|
+
PHANTOM_CONNECTOR_TYPES = %w[Association Aggregation].freeze
|
|
124
|
+
|
|
125
|
+
def phantom_connectors(diagram_row, explicit_connectors)
|
|
126
|
+
placed_ids = placed_object_ids_on(diagram_row.diagram_id)
|
|
127
|
+
explicit_conn_ids = explicit_connectors.map(&:relationship_ref).compact.to_set
|
|
128
|
+
|
|
129
|
+
all_connectors = database.collections[:connectors] || []
|
|
130
|
+
all_connectors.filter_map.with_index do |conn, idx|
|
|
131
|
+
next nil unless PHANTOM_CONNECTOR_TYPES.include?(conn.connector_type)
|
|
132
|
+
next nil unless placed_ids.include?(conn.start_object_id.to_i)
|
|
133
|
+
next nil unless placed_ids.include?(conn.end_object_id.to_i)
|
|
134
|
+
|
|
135
|
+
ref = ref_for_raw_connector(conn)
|
|
136
|
+
next nil if explicit_conn_ids.include?(ref)
|
|
137
|
+
|
|
138
|
+
build_phantom_connector(conn, diagram_row, idx)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def ref_for_raw_connector(conn)
|
|
143
|
+
IdNormalizer.from_guid(conn.ea_guid)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Default label geometry for phantom connectors. EA renders
|
|
147
|
+
# SourceRole + «property» + multiplicity at default offsets
|
|
148
|
+
# from the source endpoint when no explicit geometry exists.
|
|
149
|
+
DEFAULT_LABEL_BOXES = {
|
|
150
|
+
llb: { "ox" => 0, "oy" => 0 },
|
|
151
|
+
llt: { "ox" => 0, "oy" => 0 }
|
|
152
|
+
}.freeze
|
|
153
|
+
|
|
154
|
+
def build_phantom_connector(conn, diagram_row, idx)
|
|
155
|
+
source_placement = diagram_object_placement(diagram_row.diagram_id,
|
|
156
|
+
conn.start_object_id)
|
|
157
|
+
target_placement = diagram_object_placement(diagram_row.diagram_id,
|
|
158
|
+
conn.end_object_id)
|
|
159
|
+
return nil unless source_placement && target_placement
|
|
160
|
+
|
|
161
|
+
source_bounds = bounds_from_rect(source_placement)
|
|
162
|
+
target_bounds = bounds_from_rect(target_placement)
|
|
163
|
+
source_point = bounds_edge_point(source_bounds, target_bounds)
|
|
164
|
+
target_point = bounds_edge_point(target_bounds, source_bounds)
|
|
165
|
+
|
|
166
|
+
waypoints = [
|
|
167
|
+
Ea::Model::Waypoint.new(position: Ea::Model::Point.new(x: source_point[0], y: source_point[1])),
|
|
168
|
+
Ea::Model::Waypoint.new(position: Ea::Model::Point.new(x: target_point[0], y: target_point[1]))
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
Ea::Model::DiagramConnector.new(
|
|
172
|
+
id: IdNormalizer.synthetic("pc", diagram_row.diagram_id, idx),
|
|
173
|
+
diagram_id: IdNormalizer.from_guid(diagram_row.ea_guid),
|
|
174
|
+
relationship_ref: ref_for_raw_connector(conn),
|
|
175
|
+
connector_type: conn.connector_type,
|
|
176
|
+
direction: conn.direction,
|
|
177
|
+
waypoints: waypoints,
|
|
178
|
+
label_boxes: DEFAULT_LABEL_BOXES,
|
|
179
|
+
style: {},
|
|
180
|
+
hidden: false,
|
|
181
|
+
has_geometry_offsets: false
|
|
182
|
+
)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Computes the edge attachment point on source bounds that
|
|
186
|
+
# faces the target bounds. Uses the center-to-center direction
|
|
187
|
+
# to pick the nearest edge intersection.
|
|
188
|
+
def bounds_edge_point(bounds, other_bounds)
|
|
189
|
+
cx = bounds.x + bounds.width / 2
|
|
190
|
+
cy = bounds.y + bounds.height / 2
|
|
191
|
+
ocx = other_bounds.x + other_bounds.width / 2
|
|
192
|
+
ocy = other_bounds.y + other_bounds.height / 2
|
|
193
|
+
|
|
194
|
+
dx = ocx - cx
|
|
195
|
+
dy = ocy - cy
|
|
196
|
+
|
|
197
|
+
if dx.abs * bounds.height > dy.abs * bounds.width
|
|
198
|
+
x = dx > 0 ? bounds.x + bounds.width : bounds.x
|
|
199
|
+
y = cy + dy * (x - cx).abs / dx.abs rescue cy
|
|
200
|
+
[x, y]
|
|
201
|
+
else
|
|
202
|
+
y = dy > 0 ? bounds.y + bounds.height : bounds.y
|
|
203
|
+
x = cx + dx * (y - cy).abs / dy.abs rescue cx
|
|
204
|
+
[x, y]
|
|
205
|
+
end
|
|
58
206
|
end
|
|
59
207
|
|
|
60
208
|
def build_connector(link_row, diagram_row)
|
|
209
|
+
connector = database.find_connector(link_row.connectorid)
|
|
210
|
+
geom = parse_geometry_fields(link_row.geometry)
|
|
61
211
|
Ea::Model::DiagramConnector.new(
|
|
62
212
|
id: IdNormalizer.synthetic("dc", diagram_row.diagram_id,
|
|
63
213
|
link_row.instance_id),
|
|
64
214
|
diagram_id: IdNormalizer.from_guid(diagram_row.ea_guid),
|
|
65
215
|
relationship_ref: ref_for_connector(link_row),
|
|
216
|
+
connector_type: connector&.connector_type,
|
|
217
|
+
direction: connector&.direction,
|
|
66
218
|
waypoints: waypoints_for_link(link_row, diagram_row),
|
|
67
|
-
|
|
219
|
+
label_boxes: geom[:label_boxes] || {},
|
|
220
|
+
style: DiagramStyleParser.parse(link_row.style),
|
|
221
|
+
hidden: hidden?(link_row),
|
|
222
|
+
ghost_labels: ghost_labels_for(connector, diagram_row, link_row),
|
|
223
|
+
has_geometry_offsets: geometry_has_offsets?(link_row.geometry)
|
|
68
224
|
)
|
|
69
225
|
end
|
|
70
226
|
|
|
71
|
-
# EA's t_diagramlinks.Geometry
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
227
|
+
# EA's t_diagramlinks.Geometry may carry explicit SX/SY/EX/EY
|
|
228
|
+
# offset fields (even zeros) OR omit them entirely. The
|
|
229
|
+
# presence of SX= gates the «import» package_anchor marker:
|
|
230
|
+
# basic.qea Package Imports has SX=0;SY=0 → marker renders;
|
|
231
|
+
# plateau Urban Planning ADE2 omits SX/SY → no marker.
|
|
232
|
+
def geometry_has_offsets?(geometry_str)
|
|
233
|
+
return false if geometry_str.nil? || geometry_str.empty?
|
|
234
|
+
|
|
235
|
+
geometry_str.to_s.include?("SX=")
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# EA's t_diagramlinks.Hidden is 1 when the user has hidden
|
|
239
|
+
# the connector on this diagram (right-click → Hide). Hidden
|
|
240
|
+
# connectors are absent from the reference SVG output.
|
|
241
|
+
def hidden?(link_row)
|
|
242
|
+
link_row.hidden.to_i.nonzero?
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Builds GhostLabel entries for connector endpoints that
|
|
246
|
+
# reference objects NOT placed on the current diagram. EA
|
|
247
|
+
# renders the off-canvas classifier's name as italic text
|
|
248
|
+
# near the connector's on-canvas endpoint so the reader
|
|
249
|
+
# knows what the connector attaches to outside the diagram.
|
|
250
|
+
def ghost_labels_for(connector, diagram_row, link_row)
|
|
251
|
+
return [] unless connector
|
|
252
|
+
|
|
253
|
+
placed_ids = placed_object_ids_on(diagram_row.diagram_id)
|
|
254
|
+
ghosts = []
|
|
255
|
+
%i[source target].each do |end_kind|
|
|
256
|
+
ea_object_id = end_kind == :source ? connector.start_object_id : connector.end_object_id
|
|
257
|
+
next unless ea_object_id&.to_i&.positive?
|
|
258
|
+
next if placed_ids.include?(ea_object_id.to_i)
|
|
259
|
+
|
|
260
|
+
obj = database.find_object(ea_object_id.to_i)
|
|
261
|
+
next unless obj
|
|
262
|
+
next if obj.name.to_s.strip.empty?
|
|
263
|
+
|
|
264
|
+
anchor_point = ghost_anchor(end_kind, connector, link_row, diagram_row)
|
|
265
|
+
next unless anchor_point
|
|
266
|
+
|
|
267
|
+
ghosts << Ea::Model::GhostLabel.new(
|
|
268
|
+
id: IdNormalizer.synthetic("ghost", link_row.instance_id, ea_object_id.to_i),
|
|
269
|
+
name: obj.name.to_s,
|
|
270
|
+
end_kind: end_kind.to_s,
|
|
271
|
+
anchor_x: anchor_point[0],
|
|
272
|
+
anchor_y: anchor_point[1]
|
|
273
|
+
)
|
|
274
|
+
end
|
|
275
|
+
ghosts
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Returns the Array of EA object IDs that have a placement
|
|
279
|
+
# (t_diagramobjects row) on the given diagram.
|
|
280
|
+
def placed_object_ids_on(diagram_id)
|
|
281
|
+
objects = database.diagram_objects_for(diagram_id) || []
|
|
282
|
+
objects.map(&:ea_object_id)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Ghost label anchors at the on-canvas end of the connector.
|
|
286
|
+
# When the source is off-canvas, anchor at the target's
|
|
287
|
+
# first waypoint; vice versa for target off-canvas.
|
|
288
|
+
def ghost_anchor(end_kind, connector, link_row, diagram_row)
|
|
289
|
+
placement = if end_kind == :source
|
|
290
|
+
target_placement_for(connector, diagram_row)
|
|
291
|
+
else
|
|
292
|
+
source_placement_for(connector, diagram_row)
|
|
293
|
+
end
|
|
294
|
+
return nil unless placement
|
|
295
|
+
|
|
296
|
+
# Use the on-canvas end's edge center as the anchor.
|
|
297
|
+
b = bounds_from_rect(placement)
|
|
298
|
+
# Offset to the right of the element so the label sits
|
|
299
|
+
# outside the on-canvas element. EA's actual offset is
|
|
300
|
+
# roughly +12px in x and -8px in y from the element's
|
|
301
|
+
# right-center edge.
|
|
302
|
+
[b.x + b.width + 4, b.y + (b.height / 2) - 8]
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def source_placement_for(connector, diagram_row)
|
|
306
|
+
diagram_object_placement(diagram_row.diagram_id, connector.start_object_id)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def target_placement_for(connector, diagram_row)
|
|
310
|
+
diagram_object_placement(diagram_row.diagram_id, connector.end_object_id)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# EA stores connector routing in two places:
|
|
314
|
+
#
|
|
315
|
+
# - t_diagramlinks.Path: explicit intermediate waypoints as
|
|
316
|
+
# `x:y;x:y;...` in the element-rect coordinate frame.
|
|
317
|
+
# Present when the user has manually routed the connector
|
|
318
|
+
# or when EA's auto-router produced multi-bend paths.
|
|
319
|
+
# - t_diagramlinks.Geometry SX/SY/EX/EY: delta fields that
|
|
320
|
+
# describe the source-side and target-side bend offsets
|
|
321
|
+
# relative to the edge attachment points.
|
|
322
|
+
#
|
|
323
|
+
# Path takes precedence when present — it's the authoritative
|
|
324
|
+
# routing. SX/SY/EX/EY are fallback for auto-routed direct
|
|
325
|
+
# connectors with at most one bend on each end.
|
|
80
326
|
#
|
|
81
327
|
# If we can't resolve the source or target element placement
|
|
82
328
|
# for this diagram, we emit no waypoints (the connector will
|
|
@@ -96,21 +342,40 @@ module Ea
|
|
|
96
342
|
|
|
97
343
|
source_point = element_edge_point(source_placement, :source, edge_out)
|
|
98
344
|
target_point = element_edge_point(target_placement, :target, edge_out)
|
|
345
|
+
|
|
346
|
+
intermediate = intermediate_waypoints(link_row)
|
|
347
|
+
if intermediate.any?
|
|
348
|
+
points = [source_point, *intermediate, target_point]
|
|
349
|
+
else
|
|
350
|
+
points = sx_sy_ex_ey_waypoints(source_point, target_point, geom)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
points.map do |x, y|
|
|
354
|
+
Ea::Model::Waypoint.new(position: Ea::Model::Point.new(x: x, y: y))
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# Parse t_diagramlinks.Path into [[x, y], ...] intermediate
|
|
359
|
+
# waypoints. Format: `x1:y1;x2:y2;...`. Empty string returns [].
|
|
360
|
+
def intermediate_waypoints(link_row)
|
|
361
|
+
raw = link_row.path.to_s
|
|
362
|
+
return [] if raw.empty?
|
|
363
|
+
|
|
364
|
+
raw.scan(/(-?\d+):(-?\d+)/).map { |x, y| [x.to_i, y.to_i] }
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# Fallback when no explicit Path: compute source/target bend
|
|
368
|
+
# points from the SX/SY/EX/EY delta fields.
|
|
369
|
+
def sx_sy_ex_ey_waypoints(source_point, target_point, geom)
|
|
99
370
|
points = [source_point]
|
|
100
|
-
# SX/SY: delta from source point to first bend.
|
|
101
371
|
if geom[:sx] && geom[:sy] && (geom[:sx].nonzero? || geom[:sy].nonzero?)
|
|
102
372
|
points << [source_point[0] + geom[:sx], source_point[1] + geom[:sy]]
|
|
103
373
|
end
|
|
104
|
-
# EX/EY: delta from second bend to target point. Reverse
|
|
105
|
-
# to get from target back to the bend.
|
|
106
374
|
if geom[:ex] && geom[:ey] && (geom[:ex].nonzero? || geom[:ey].nonzero?)
|
|
107
375
|
points << [target_point[0] - geom[:ex], target_point[1] - geom[:ey]]
|
|
108
376
|
end
|
|
109
377
|
points << target_point
|
|
110
|
-
|
|
111
|
-
points.map do |x, y|
|
|
112
|
-
Ea::Model::Waypoint.new(position: Ea::Model::Point.new(x: x, y: y))
|
|
113
|
-
end
|
|
378
|
+
points
|
|
114
379
|
end
|
|
115
380
|
|
|
116
381
|
# Parse the SX, SY, EX, EY, EDGE key=value pairs out of the
|
|
@@ -126,7 +391,61 @@ module Ea
|
|
|
126
391
|
sy: pick_int(s, /SY=(-?\d+)/),
|
|
127
392
|
ex: pick_int(s, /EX=(-?\d+)/),
|
|
128
393
|
ey: pick_int(s, /EY=(-?\d+)/),
|
|
129
|
-
edge: pick_int(s, /EDGE=(-?\d+)/)
|
|
394
|
+
edge: pick_int(s, /EDGE=(-?\d+)/),
|
|
395
|
+
label_boxes: parse_label_boxes(s)
|
|
396
|
+
}
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Parse EA's $LLB=, $LLT=, $LRB=, $LRT= label box geometry.
|
|
400
|
+
# Format: $LLB=CX=<int>:CY=<int>:OX=<int>:OY=<int>:HDN=0:BLD=0:
|
|
401
|
+
# ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:ROT=0;
|
|
402
|
+
# Only the FIRST segment carries the leading `$`; subsequent
|
|
403
|
+
# segments use bare `KEY=` after the semicolon separator.
|
|
404
|
+
#
|
|
405
|
+
# Returns Hash keyed by :llb, :llt, :lrt, :lrb (and optional
|
|
406
|
+
# :lmt, :lmb, :irhs, :ilhs). Each value is a Hash with:
|
|
407
|
+
# - "ox", "oy" — label offset from anchor (always set)
|
|
408
|
+
# - "cx", "cy" — cell size for label box
|
|
409
|
+
# - "hidden" — bool, label suppressed when true
|
|
410
|
+
# - "bold", "italic", "underline" — bool font flags
|
|
411
|
+
# - "color" — signed int (VB OLE_COLOR; -1 = default)
|
|
412
|
+
# - "alignment" — 0=left, 1=center, 2=right
|
|
413
|
+
# - "direction" — text direction enum
|
|
414
|
+
# - "rotation" — degrees
|
|
415
|
+
def parse_label_boxes(geom_str)
|
|
416
|
+
boxes = {}
|
|
417
|
+
%i[llb llt lrt lrb lmt lmb irhs ilhs].each do |key|
|
|
418
|
+
match = geom_str.match(/(?:\$|;)\s*#{key.to_s.upcase}=([^;]*)/i)
|
|
419
|
+
next unless match && !match[1].empty?
|
|
420
|
+
|
|
421
|
+
parsed = parse_label_style(match[1])
|
|
422
|
+
next unless parsed
|
|
423
|
+
|
|
424
|
+
boxes[key] = parsed
|
|
425
|
+
end
|
|
426
|
+
boxes
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
# Parse one label box's colon-separated key=value body into
|
|
430
|
+
# a Hash with string keys. Returns nil if OX/OY are absent.
|
|
431
|
+
def parse_label_style(body)
|
|
432
|
+
ox = body.match(/OX=(-?\d+)/)
|
|
433
|
+
oy = body.match(/OY=(-?\d+)/)
|
|
434
|
+
return nil unless ox && oy
|
|
435
|
+
|
|
436
|
+
{
|
|
437
|
+
"ox" => ox[1].to_i,
|
|
438
|
+
"oy" => oy[1].to_i,
|
|
439
|
+
"cx" => pick_int(body, /CX=(-?\d+)/),
|
|
440
|
+
"cy" => pick_int(body, /CY=(-?\d+)/),
|
|
441
|
+
"hidden" => pick_int(body, /HDN=(\d+)/) == 1,
|
|
442
|
+
"bold" => pick_int(body, /BLD=(\d+)/) == 1,
|
|
443
|
+
"italic" => pick_int(body, /ITA=(\d+)/) == 1,
|
|
444
|
+
"underline" => pick_int(body, /UND=(\d+)/) == 1,
|
|
445
|
+
"color" => pick_int(body, /CLR=(-?\d+)/),
|
|
446
|
+
"alignment" => pick_int(body, /ALN=(\d+)/),
|
|
447
|
+
"direction" => pick_int(body, /DIR=(\d+)/),
|
|
448
|
+
"rotation" => pick_int(body, /ROT=(-?\d+)/)
|
|
130
449
|
}
|
|
131
450
|
end
|
|
132
451
|
|
|
@@ -186,12 +505,21 @@ module Ea
|
|
|
186
505
|
)
|
|
187
506
|
end
|
|
188
507
|
|
|
508
|
+
# EA's t_diagramobjects stores rect with the origin at the
|
|
509
|
+
# bottom-left (math convention) — recttop > rectbottom for
|
|
510
|
+
# valid bounds, and y values are often negative. Convert to
|
|
511
|
+
# screen-space (origin top-left) by taking min/max and using
|
|
512
|
+
# absolute width/height.
|
|
189
513
|
def bounds_from_rect(obj_row)
|
|
514
|
+
left = obj_row.rectleft || 0
|
|
515
|
+
right = obj_row.rectright || 0
|
|
516
|
+
top = obj_row.recttop || 0
|
|
517
|
+
bottom = obj_row.rectbottom || 0
|
|
190
518
|
Ea::Model::Bounds.new(
|
|
191
|
-
x:
|
|
192
|
-
y:
|
|
193
|
-
width: (
|
|
194
|
-
height: (
|
|
519
|
+
x: [left, right].min,
|
|
520
|
+
y: [top, bottom].min,
|
|
521
|
+
width: (right - left).abs,
|
|
522
|
+
height: (bottom - top).abs
|
|
195
523
|
)
|
|
196
524
|
end
|
|
197
525
|
|