ea 0.4.0 → 0.5.1
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/CHANGELOG.md +128 -1
- 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/51-remove-respond-to.md +25 -0
- data/TODO.complete/52-xsd-decouple-fixtures.md +23 -0
- data/TODO.complete/53-xmi-public-send-cleanup.md +26 -0
- data/TODO.complete/54-wire-stereotype-icon-renderer.md +20 -0
- data/TODO.complete/55-wire-shapescript.md +20 -0
- data/TODO.complete/56-wire-emf-renderer.md +27 -0
- data/TODO.complete/57-wire-ocl-evaluator.md +23 -0
- data/TODO.complete/58-diff-modifications.md +21 -0
- data/TODO.complete/59-delete-toy-xmi-export.md +30 -0
- data/TODO.complete/60-curate-json-export.md +20 -0
- data/TODO.complete/61-plantuml-relationships.md +22 -0
- data/TODO.complete/62-changelog.md +17 -0
- data/TODO.complete/63-dependabot-fix.md +18 -0
- data/TODO.complete/STATUS.md +85 -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 +101 -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 +109 -0
- data/lib/ea/diff/html_reporter.rb +91 -0
- data/lib/ea/diff.rb +15 -0
- data/lib/ea/export/json/generator.rb +118 -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 +140 -0
- data/lib/ea/export/plantuml.rb +12 -0
- data/lib/ea/export/xsd/class_mapping.rb +65 -0
- data/lib/ea/export/xsd/generator.rb +137 -0
- data/lib/ea/export/xsd/namespace_registry.rb +82 -0
- data/lib/ea/export/xsd.rb +19 -0
- data/lib/ea/export.rb +13 -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 +40 -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/qea/validation/database/ocl_constraint_validator.rb +65 -0
- data/lib/ea/qea/validation/database.rb +2 -0
- data/lib/ea/qea/validation/validation_engine.rb +2 -0
- data/lib/ea/qea/validation.rb +2 -0
- data/lib/ea/query/builder.rb +127 -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/stereotype_icon.rb +32 -0
- data/lib/ea/svg/ea_emitter/compartment/tagged_values.rb +30 -0
- data/lib/ea/svg/ea_emitter/compartment.rb +60 -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 +49 -25
- 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 +119 -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/validation/xmi_parity.rb +80 -0
- data/lib/ea/validation.rb +12 -0
- data/lib/ea/version.rb +4 -1
- data/lib/ea.rb +15 -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 +464 -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
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Export
|
|
5
|
+
module PlantUml
|
|
6
|
+
# Generates PlantUML class diagram text from a parsed QEA model.
|
|
7
|
+
#
|
|
8
|
+
# Emits:
|
|
9
|
+
# - Package nesting (`package "X" { ... }`)
|
|
10
|
+
# - Class declarations with attributes and operations
|
|
11
|
+
# - Generalizations (`Parent <|-- Child`)
|
|
12
|
+
# - Associations with multiplicities and role names
|
|
13
|
+
# - Enumerations as `enum` blocks
|
|
14
|
+
class Generator
|
|
15
|
+
def self.call(model, **_opts)
|
|
16
|
+
new(model).call
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_reader :model
|
|
20
|
+
|
|
21
|
+
def initialize(model)
|
|
22
|
+
@model = model
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def call
|
|
26
|
+
lines = ["@startuml", "skinparam classAttributeIconSize 0"]
|
|
27
|
+
lines.concat(package_lines)
|
|
28
|
+
lines.concat(connector_lines)
|
|
29
|
+
lines << "@enduml"
|
|
30
|
+
lines.join("\n") + "\n"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
# Wrap classes/enums in their containing packages.
|
|
36
|
+
def package_lines
|
|
37
|
+
packages_by_id = packages_index
|
|
38
|
+
root_objects = (model.collections[:objects] || [])
|
|
39
|
+
|
|
40
|
+
# Group classes by their package_id; emit each package.
|
|
41
|
+
grouped = root_objects.group_by(&:package_id)
|
|
42
|
+
grouped.flat_map do |pkg_id, objs|
|
|
43
|
+
next ungrouped_lines(objs) if pkg_id.nil?
|
|
44
|
+
|
|
45
|
+
pkg = packages_by_id[pkg_id]
|
|
46
|
+
pkg_name = pkg&.name || "Package#{pkg_id}"
|
|
47
|
+
inner = objs.flat_map { |o| class_block(o) }
|
|
48
|
+
next inner if inner.empty?
|
|
49
|
+
|
|
50
|
+
["package \"#{pkg_name}\" {",
|
|
51
|
+
*inner.map { |line| " #{line}" },
|
|
52
|
+
"}"]
|
|
53
|
+
end.compact
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def ungrouped_lines(objs)
|
|
57
|
+
objs.flat_map { |o| class_block(o) }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Emit `class X { +attr: T ... }` or `enum X { LITERAL1 ... }`.
|
|
61
|
+
def class_block(obj)
|
|
62
|
+
case obj.object_type
|
|
63
|
+
when "Class"
|
|
64
|
+
attrs = class_attributes(obj)
|
|
65
|
+
body = attrs.map { |a| "+#{a.name}: #{a.type || 'String'}" }
|
|
66
|
+
return ["class #{obj.name}"] if body.empty?
|
|
67
|
+
|
|
68
|
+
["class #{obj.name} {", *body.map { |b| " #{b}" }, "}"]
|
|
69
|
+
when "Enumeration"
|
|
70
|
+
literals = enum_literals(obj)
|
|
71
|
+
return ["enum #{obj.name}"] if literals.empty?
|
|
72
|
+
|
|
73
|
+
["enum #{obj.name} {",
|
|
74
|
+
*literals.map { |l| " #{l.name || l}" },
|
|
75
|
+
"}"]
|
|
76
|
+
when "Interface"
|
|
77
|
+
["interface #{obj.name}"]
|
|
78
|
+
when "DataType"
|
|
79
|
+
["abstract class #{obj.name}"]
|
|
80
|
+
else
|
|
81
|
+
["class #{obj.name}"]
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Generalizations: `<|`-style inheritance.
|
|
86
|
+
# Associations: multiplicity + role names.
|
|
87
|
+
def connector_lines
|
|
88
|
+
(model.collections[:connectors] || []).filter_map do |conn|
|
|
89
|
+
src = find_object(conn.start_object_id)
|
|
90
|
+
tgt = find_object(conn.end_object_id)
|
|
91
|
+
next nil unless src && tgt
|
|
92
|
+
|
|
93
|
+
case conn.connector_type
|
|
94
|
+
when "Generalization"
|
|
95
|
+
"#{tgt.name} <|-- #{src.name}"
|
|
96
|
+
when "Association", "Aggregation", "Composition"
|
|
97
|
+
assoc_arrow(src, tgt, conn)
|
|
98
|
+
else
|
|
99
|
+
label = conn.name ? " : #{conn.name}" : ""
|
|
100
|
+
"#{src.name} --> #{tgt.name}#{label}"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def assoc_arrow(src, tgt, conn)
|
|
106
|
+
arrow = case conn.connector_type
|
|
107
|
+
when "Composition" then "*--"
|
|
108
|
+
when "Aggregation" then "o--"
|
|
109
|
+
else "--"
|
|
110
|
+
end
|
|
111
|
+
label = conn.name ? " : #{conn.name}" : ""
|
|
112
|
+
"#{src.name} #{arrow} #{tgt.name}#{label}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def packages_index
|
|
116
|
+
(model.collections[:packages] || []).each_with_object({}) do |p, acc|
|
|
117
|
+
acc[p.package_id] = p
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def class_attributes(klass)
|
|
122
|
+
attrs = model.collections[:attributes] || []
|
|
123
|
+
attrs.select { |a| a.ea_object_id == klass.ea_object_id }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def enum_literals(enum)
|
|
127
|
+
literals = model.collections[:enumeration_literals] ||
|
|
128
|
+
model.collections[:enum_literals] || []
|
|
129
|
+
# Enumeration literals are linked to the enum via object_id.
|
|
130
|
+
literals.select { |l| l.respond_to?(:ea_object_id) && l.ea_object_id == enum.ea_object_id }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def find_object(object_id)
|
|
134
|
+
(model.collections[:objects] || [])
|
|
135
|
+
.find { |o| o.ea_object_id == object_id || o.object_id == object_id }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Export
|
|
5
|
+
# PlantUML text-format exporter. Produces a `.puml` file
|
|
6
|
+
# documenting the model's classes, attributes, and relationships
|
|
7
|
+
# in PlantUML syntax (rendered by plantuml.com/plantuml).
|
|
8
|
+
module PlantUml
|
|
9
|
+
autoload :Generator, "ea/export/plantuml/generator"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
|
|
5
|
+
module Ea
|
|
6
|
+
module Export
|
|
7
|
+
module Xsd
|
|
8
|
+
# Loads EA's GMLClassMapping.xml and exposes per-class mapping
|
|
9
|
+
# rules: UML class name → GML element/type/propertyType names.
|
|
10
|
+
#
|
|
11
|
+
# XML format:
|
|
12
|
+
# <ClassMapping>
|
|
13
|
+
# <Class name="FeatureType" element="featureMember"
|
|
14
|
+
# type="FeaturePropertyType" propertyType="..."
|
|
15
|
+
# typeContent="complex"/>
|
|
16
|
+
# </ClassMapping>
|
|
17
|
+
class ClassMapping
|
|
18
|
+
attr_reader :mappings
|
|
19
|
+
|
|
20
|
+
# @param mappings [Hash{String => Mapping}] keyed by UML class name
|
|
21
|
+
def initialize(mappings = {})
|
|
22
|
+
@mappings = mappings
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
Mapping = Struct.new(:name, :element, :type, :property_type,
|
|
26
|
+
:type_content, keyword_init: true)
|
|
27
|
+
|
|
28
|
+
# @param path [String] path to GMLClassMapping.xml
|
|
29
|
+
# @return [ClassMapping]
|
|
30
|
+
def self.from_path(path)
|
|
31
|
+
return new if path.nil? || !File.exist?(path)
|
|
32
|
+
|
|
33
|
+
from_nokogiri(Nokogiri::XML(File.read(path)))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @param doc [Nokogiri::XML::Document]
|
|
37
|
+
# @return [ClassMapping]
|
|
38
|
+
def self.from_nokogiri(doc)
|
|
39
|
+
mappings = {}
|
|
40
|
+
doc.xpath("//Class").each do |node|
|
|
41
|
+
name = node["name"]
|
|
42
|
+
next unless name
|
|
43
|
+
|
|
44
|
+
mappings[name] = Mapping.new(
|
|
45
|
+
name: name,
|
|
46
|
+
element: node["element"],
|
|
47
|
+
type: node["type"],
|
|
48
|
+
property_type: node["propertyType"],
|
|
49
|
+
type_content: node["typeContent"]
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
new(mappings)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Look up a mapping by UML class name. Returns nil when no
|
|
56
|
+
# explicit mapping exists.
|
|
57
|
+
# @param class_name [String]
|
|
58
|
+
# @return [Mapping, nil]
|
|
59
|
+
def for(class_name)
|
|
60
|
+
mappings[class_name]
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
|
|
5
|
+
module Ea
|
|
6
|
+
module Export
|
|
7
|
+
module Xsd
|
|
8
|
+
# Generates an XSD schema string from a parsed QEA model.
|
|
9
|
+
#
|
|
10
|
+
# Pure: takes already-loaded ClassMapping and NamespaceRegistry
|
|
11
|
+
# instances. No file IO, no fixture-path coupling. The CLI
|
|
12
|
+
# layer is responsible for loading GMLClassMapping.xml and
|
|
13
|
+
# GMLNamespaces.xml from disk.
|
|
14
|
+
class Generator
|
|
15
|
+
XSD_NS = "http://www.w3.org/2001/XMLSchema"
|
|
16
|
+
|
|
17
|
+
attr_reader :class_mapping, :namespaces
|
|
18
|
+
|
|
19
|
+
# @param class_mapping [ClassMapping] type translation rules
|
|
20
|
+
# @param namespaces [NamespaceRegistry] namespace declarations
|
|
21
|
+
def initialize(class_mapping: ClassMapping.new,
|
|
22
|
+
namespaces: NamespaceRegistry.new)
|
|
23
|
+
@class_mapping = class_mapping
|
|
24
|
+
@namespaces = namespaces
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Class-method shortcut. Accepts the same options as #initialize
|
|
28
|
+
# via keyword args.
|
|
29
|
+
def self.call(model, target_namespace: "http://example.com/xsd",
|
|
30
|
+
prefix: "tns", **opts)
|
|
31
|
+
new(**opts).call(model, target_namespace: target_namespace, prefix: prefix)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Generate an XSD schema for the given model.
|
|
35
|
+
# @param model [#collections] anything responding to #collections
|
|
36
|
+
# with a :packages and :objects collection (Ea::Qea::Database).
|
|
37
|
+
# @param target_namespace [String] XSD targetNamespace
|
|
38
|
+
# @param prefix [String] namespace prefix for the target
|
|
39
|
+
# @return [String] serialized XSD
|
|
40
|
+
def call(model, target_namespace:, prefix: "tns")
|
|
41
|
+
doc = build_schema_doc(target_namespace, prefix)
|
|
42
|
+
walk_model(model, doc)
|
|
43
|
+
doc.to_xml(indent: 2)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def build_schema_doc(target_namespace, prefix)
|
|
49
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
|
50
|
+
xml.schema("xmlns" => XSD_NS,
|
|
51
|
+
"xmlns:#{prefix}" => target_namespace,
|
|
52
|
+
"targetNamespace" => target_namespace,
|
|
53
|
+
"elementFormDefault" => "qualified")
|
|
54
|
+
end
|
|
55
|
+
builder.doc
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def walk_model(model, doc)
|
|
59
|
+
classes = model_classes(model)
|
|
60
|
+
classes.each { |klass| emit_class(doc, doc.root, klass) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Extract UML-like classes from the model. Ea::Qea::Database
|
|
64
|
+
# stores them in :objects; we filter to classes (Object_Type
|
|
65
|
+
# == "Class") and emit a struct for the generator's needs.
|
|
66
|
+
Classifier = Struct.new(:name, :stereotype, :attributes,
|
|
67
|
+
keyword_init: true)
|
|
68
|
+
|
|
69
|
+
def model_classes(model)
|
|
70
|
+
objects = model.collections[:objects] || []
|
|
71
|
+
objects.select { |o| o.object_type == "Class" }.map do |obj|
|
|
72
|
+
Classifier.new(
|
|
73
|
+
name: obj.name,
|
|
74
|
+
stereotype: stereotype_for(model, obj),
|
|
75
|
+
attributes: attributes_for(model, obj)
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def stereotype_for(model, obj)
|
|
81
|
+
# Walk t_xref for the object's GUID; pick first @STEREO.
|
|
82
|
+
xrefs = model.collections[:xrefs] || []
|
|
83
|
+
stereo_xref = xrefs.find do |xr|
|
|
84
|
+
xr.client == obj.ea_guid && xr.description&.include?("@STEREO")
|
|
85
|
+
end
|
|
86
|
+
return nil unless stereo_xref
|
|
87
|
+
|
|
88
|
+
match = stereo_xref.description.match(/Name=([^;]+)/)
|
|
89
|
+
match ? match[1] : nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def attributes_for(model, obj)
|
|
93
|
+
attrs = model.collections[:attributes] || []
|
|
94
|
+
attrs.select { |a| a.object_id == obj.object_id }
|
|
95
|
+
.map { |a| { name: a.name, type: a.type || "string" } }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def emit_class(doc, schema_root, classifier)
|
|
99
|
+
mapping = class_mapping.for(classifier.name) ||
|
|
100
|
+
class_mapping.for(classifier.stereotype.to_s)
|
|
101
|
+
|
|
102
|
+
type_name = mapping&.type || "#{classifier.name}Type"
|
|
103
|
+
element_name = mapping&.element || classifier.name&.downcase
|
|
104
|
+
|
|
105
|
+
schema_root.add_child(element_node(doc, element_name, type_name))
|
|
106
|
+
schema_root.add_child(complex_type_node(doc, type_name, classifier))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def element_node(doc, name, type_name)
|
|
110
|
+
el = doc.create_element("element", "xmlns" => XSD_NS)
|
|
111
|
+
el["name"] = name
|
|
112
|
+
el["type"] = "tns:#{type_name}"
|
|
113
|
+
el
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def complex_type_node(doc, type_name, classifier)
|
|
117
|
+
ct = doc.create_element("complexType", "xmlns" => XSD_NS)
|
|
118
|
+
ct["name"] = type_name
|
|
119
|
+
seq = doc.create_element("sequence", "xmlns" => XSD_NS)
|
|
120
|
+
classifier.attributes.each do |attr|
|
|
121
|
+
seq.add_child(attribute_element(doc, attr))
|
|
122
|
+
end
|
|
123
|
+
ct.add_child(seq)
|
|
124
|
+
ct
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def attribute_element(doc, attr)
|
|
128
|
+
el = doc.create_element("element", "xmlns" => XSD_NS)
|
|
129
|
+
el["name"] = attr[:name].to_s
|
|
130
|
+
el["type"] = "xs:string"
|
|
131
|
+
el["minOccurs"] = "0"
|
|
132
|
+
el
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
|
|
5
|
+
module Ea
|
|
6
|
+
module Export
|
|
7
|
+
module Xsd
|
|
8
|
+
# Loads EA's GMLNamespaces.xml and exposes per-prefix namespace
|
|
9
|
+
# declarations: prefix → { target_namespace:, xsd_document: }.
|
|
10
|
+
#
|
|
11
|
+
# XML format:
|
|
12
|
+
# <Namespaces>
|
|
13
|
+
# <GMLNS version="3.2.1">
|
|
14
|
+
# <Namespace xmlns="gml" targetNamespace="http://..."
|
|
15
|
+
# xsdDocument="http://.../gml.xsd"/>
|
|
16
|
+
# </GMLNS>
|
|
17
|
+
# </Namespaces>
|
|
18
|
+
class NamespaceRegistry
|
|
19
|
+
attr_reader :namespaces
|
|
20
|
+
|
|
21
|
+
# @param namespaces [Hash{String => Namespace}]
|
|
22
|
+
def initialize(namespaces = {})
|
|
23
|
+
@namespaces = namespaces
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
Namespace = Struct.new(:prefix, :target_namespace, :xsd_document,
|
|
27
|
+
:version, keyword_init: true)
|
|
28
|
+
|
|
29
|
+
# @param path [String] path to GMLNamespaces.xml
|
|
30
|
+
# @param version [String, nil] GML version filter (e.g. "3.2.1")
|
|
31
|
+
# @return [NamespaceRegistry]
|
|
32
|
+
def self.from_path(path, version: nil)
|
|
33
|
+
return new if path.nil? || !File.exist?(path)
|
|
34
|
+
|
|
35
|
+
from_nokogiri(Nokogiri::XML(File.read(path)), version: version)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @param doc [Nokogiri::XML::Document]
|
|
39
|
+
# @param version [String, nil] GML version filter
|
|
40
|
+
# @return [NamespaceRegistry]
|
|
41
|
+
def self.from_nokogiri(doc, version: nil)
|
|
42
|
+
namespaces = {}
|
|
43
|
+
# EA stores the prefix in a `xmlns` attribute, which XML
|
|
44
|
+
# reserves for namespace declarations. Nokogiri interprets
|
|
45
|
+
# it as the element's namespace, so we read it back via
|
|
46
|
+
# `namespace` (the parsed NS) rather than `attribute`.
|
|
47
|
+
# Use local-name() so the lookup is namespace-agnostic.
|
|
48
|
+
doc.xpath("//*[local-name()='GMLNS']").each do |gmlns|
|
|
49
|
+
next if version && gmlns["version"] != version
|
|
50
|
+
|
|
51
|
+
gmlns.element_children.each do |node|
|
|
52
|
+
next unless node.name == "Namespace"
|
|
53
|
+
|
|
54
|
+
prefix = node.namespace&.href
|
|
55
|
+
next unless prefix
|
|
56
|
+
|
|
57
|
+
namespaces[prefix] = Namespace.new(
|
|
58
|
+
prefix: prefix,
|
|
59
|
+
target_namespace: node["targetNamespace"],
|
|
60
|
+
xsd_document: node["xsdDocument"],
|
|
61
|
+
version: gmlns["version"]
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
new(namespaces)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @param prefix [String]
|
|
69
|
+
# @return [Namespace, nil]
|
|
70
|
+
def for(prefix)
|
|
71
|
+
namespaces[prefix]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# All distinct versions present (e.g. ["3.2.1", "3.3"]).
|
|
75
|
+
# @return [Array<String>]
|
|
76
|
+
def versions
|
|
77
|
+
namespaces.values.map(&:version).compact.uniq
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Export
|
|
5
|
+
# XSD (XML Schema Definition) export namespace.
|
|
6
|
+
#
|
|
7
|
+
# Translates a parsed QEA/UML model into an XSD schema by:
|
|
8
|
+
# 1. Reading GMLClassMapping.xml to learn how UML classes map to
|
|
9
|
+
# GML types (element/type/propertyType names).
|
|
10
|
+
# 2. Reading GMLNamespaces.xml to learn target namespaces.
|
|
11
|
+
# 3. Walking the model's classes and emitting `<xs:element>` +
|
|
12
|
+
# `<xs:complexType>` declarations for each.
|
|
13
|
+
module Xsd
|
|
14
|
+
autoload :ClassMapping, "ea/export/xsd/class_mapping"
|
|
15
|
+
autoload :NamespaceRegistry, "ea/export/xsd/namespace_registry"
|
|
16
|
+
autoload :Generator, "ea/export/xsd/generator"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/ea/export.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
# Export namespace — format-specific exporters (XSD, PlantUML, etc.).
|
|
5
|
+
# One sub-namespace per output format; each is independent and
|
|
6
|
+
# autoloadable.
|
|
7
|
+
module Export
|
|
8
|
+
autoload :Json, "ea/export/json"
|
|
9
|
+
autoload :JsonSchema, "ea/export/json_schema"
|
|
10
|
+
autoload :PlantUml, "ea/export/plantuml"
|
|
11
|
+
autoload :Xsd, "ea/export/xsd"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Image
|
|
5
|
+
# Converts EA's t_image EMF blobs to SVG strings by delegating to
|
|
6
|
+
# the pure-Ruby `emfsvg` gem. Lazy-loads the gem on first call.
|
|
7
|
+
#
|
|
8
|
+
# When emfsvg is unavailable, returns nil — callers should fall
|
|
9
|
+
# back to skipping image rendering. This keeps the gem optional
|
|
10
|
+
# rather than a hard runtime requirement.
|
|
11
|
+
module EmfRenderer
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
# Convert an EMF blob (raw bytes) to an SVG string.
|
|
15
|
+
#
|
|
16
|
+
# @param bytes [String, nil] EMF binary blob from t_image.Image
|
|
17
|
+
# @param options [Hash] forwarded to Emfsvg.from_bytes
|
|
18
|
+
# @return [String, nil] SVG markup, or nil if emfsvg unavailable
|
|
19
|
+
def render(bytes, **options)
|
|
20
|
+
return nil if bytes.nil? || bytes.empty?
|
|
21
|
+
return nil unless available?
|
|
22
|
+
|
|
23
|
+
Emfsvg.from_bytes(bytes, **options)
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
warn "[ea] emfsvg render failed: #{e.message}" if ENV["EA_DEBUG"]
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @return [Boolean] true if the emfsvg gem can be loaded
|
|
30
|
+
def available?
|
|
31
|
+
return @available if defined?(@available)
|
|
32
|
+
|
|
33
|
+
begin
|
|
34
|
+
require "emfsvg"
|
|
35
|
+
@available = !!Emfsvg
|
|
36
|
+
rescue LoadError
|
|
37
|
+
@available = false
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/ea/image.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
# Image rendering namespace. Converts EA's t_image EMF blobs to SVG.
|
|
5
|
+
#
|
|
6
|
+
# The emfsvg gem is an OPTIONAL runtime dependency. If it isn't
|
|
7
|
+
# loadable, callers degrade gracefully (image is skipped). This
|
|
8
|
+
# keeps the ea gem usable without forcing emfsvg on every install.
|
|
9
|
+
module Image
|
|
10
|
+
autoload :EmfRenderer, "ea/image/emf_renderer"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Lint
|
|
5
|
+
# Runs all registered rules against a model and collects offenses.
|
|
6
|
+
# Adding a rule = adding it to DEFAULT_RULES (OCP).
|
|
7
|
+
class Engine
|
|
8
|
+
attr_reader :rules
|
|
9
|
+
|
|
10
|
+
DEFAULT_RULES = [
|
|
11
|
+
Rules::NamingConventionRule,
|
|
12
|
+
Rules::OrphanElementRule,
|
|
13
|
+
Rules::DuplicateNameRule,
|
|
14
|
+
Rules::CyclicGeneralizationRule,
|
|
15
|
+
Rules::MissingStereotypeRule
|
|
16
|
+
].freeze
|
|
17
|
+
|
|
18
|
+
# @param rules [Array<Class>] LintRule subclasses
|
|
19
|
+
def initialize(rules: DEFAULT_RULES)
|
|
20
|
+
@rules = rules
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @param model [#collections]
|
|
24
|
+
# @return [Array<Offense>]
|
|
25
|
+
def run(model)
|
|
26
|
+
@rules.flat_map { |rule_class| rule_class.new.check(model) }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Lint
|
|
5
|
+
# Base class for lint rules. Subclasses implement #check(model)
|
|
6
|
+
# and return an Array<Offense>. Default severity is :warning;
|
|
7
|
+
# subclasses can override via `self.severity`.
|
|
8
|
+
class LintRule
|
|
9
|
+
class << self
|
|
10
|
+
attr_accessor :severity
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.name
|
|
14
|
+
to_s.split("::").last.gsub(/Rule\z/, "").gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.severity
|
|
18
|
+
@severity || :warning
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def check(_model)
|
|
22
|
+
raise NotImplementedError, "#{self.class}#check not implemented"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
protected
|
|
26
|
+
|
|
27
|
+
def offense(entity_id:, entity_name:, message:, severity: nil)
|
|
28
|
+
Offense.new(
|
|
29
|
+
rule: self.class.name,
|
|
30
|
+
severity: severity || self.class.severity,
|
|
31
|
+
entity_id: entity_id,
|
|
32
|
+
entity_name: entity_name,
|
|
33
|
+
message: message
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Lint
|
|
5
|
+
# One lint offense. Carries rule name, severity, entity id/name,
|
|
6
|
+
# and a human-readable message.
|
|
7
|
+
Offense = Struct.new(:rule, :severity, :entity_id, :entity_name,
|
|
8
|
+
:message, keyword_init: true) do
|
|
9
|
+
def error?
|
|
10
|
+
severity == :error
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def warning?
|
|
14
|
+
severity == :warning
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Lint
|
|
5
|
+
module Rules
|
|
6
|
+
# Detects cyclic generalization chains (A → B → A).
|
|
7
|
+
class CyclicGeneralizationRule < LintRule
|
|
8
|
+
self.severity = :error
|
|
9
|
+
|
|
10
|
+
def check(model)
|
|
11
|
+
graph = build_graph(model)
|
|
12
|
+
cycles = find_cycles(graph)
|
|
13
|
+
cycles.map do |cycle|
|
|
14
|
+
offense(entity_id: cycle.first,
|
|
15
|
+
entity_name: cycle.join(" → "),
|
|
16
|
+
message: "Cyclic generalization: #{cycle.join(' → ')} → #{cycle.first}")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def build_graph(model)
|
|
23
|
+
graph = {}
|
|
24
|
+
(model.collections[:connectors] || []).each do |conn|
|
|
25
|
+
next unless conn.connector_type == "Generalization"
|
|
26
|
+
|
|
27
|
+
from = conn.start_object_id
|
|
28
|
+
to = conn.end_object_id
|
|
29
|
+
graph[from] ||= []
|
|
30
|
+
graph[from] << to
|
|
31
|
+
end
|
|
32
|
+
graph
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def find_cycles(graph)
|
|
36
|
+
cycles = []
|
|
37
|
+
visited = Set.new
|
|
38
|
+
|
|
39
|
+
graph.each_key do |start|
|
|
40
|
+
next if visited.include?(start)
|
|
41
|
+
|
|
42
|
+
dfs(graph, start, [start], Set.new([start]), cycles, visited)
|
|
43
|
+
end
|
|
44
|
+
cycles
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def dfs(graph, node, path, on_path, cycles, visited)
|
|
48
|
+
(graph[node] || []).each do |neighbor|
|
|
49
|
+
if on_path.include?(neighbor)
|
|
50
|
+
idx = path.index(neighbor)
|
|
51
|
+
cycles << path[idx..] + [neighbor] if idx
|
|
52
|
+
elsif !visited.include?(neighbor)
|
|
53
|
+
dfs(graph, neighbor, path + [neighbor],
|
|
54
|
+
on_path + [neighbor], cycles, visited)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
visited.add(node)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|