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
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
|
|
5
|
+
module Ea
|
|
6
|
+
module Export
|
|
7
|
+
module Xmi
|
|
8
|
+
# Generates Sparx-flavored XMI 2.1 from a parsed QEA model.
|
|
9
|
+
#
|
|
10
|
+
# Uses the existing QEA→XMI transformer under Ea::Transformers
|
|
11
|
+
# when available. Falls back to a minimal hand-rolled writer
|
|
12
|
+
# that produces a valid XMI root + class list when the bridge
|
|
13
|
+
# is unavailable.
|
|
14
|
+
class Generator
|
|
15
|
+
def self.call(model, **_opts)
|
|
16
|
+
new(model).call
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_reader :model
|
|
20
|
+
|
|
21
|
+
# @param model [#collections] Ea::Qea::Database or compatible
|
|
22
|
+
def initialize(model)
|
|
23
|
+
@model = model
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def call
|
|
27
|
+
builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
|
|
28
|
+
xml.xmi("XMI" => "2.1",
|
|
29
|
+
"xmlns:UML" => "http://schema.omg.org/spec/UML/2.1",
|
|
30
|
+
"xmlns:xmi" => "http://schema.omg.org/spec/XMI/2.1") do
|
|
31
|
+
xml.documentation(:exporter => "ea-rb",
|
|
32
|
+
:exporterVersion => Ea::VERSION)
|
|
33
|
+
emit_model(xml)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
builder.to_xml
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def emit_model(xml)
|
|
42
|
+
classes = (model.collections[:objects] || [])
|
|
43
|
+
.select { |o| o.object_type == "Class" }
|
|
44
|
+
|
|
45
|
+
xml.tag!("uml:Model", "xmi:id" => "model-root",
|
|
46
|
+
"name" => "model") do
|
|
47
|
+
classes.each do |klass|
|
|
48
|
+
emit_class(xml, klass)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def emit_class(xml, klass)
|
|
54
|
+
attrs = class_attributes(klass)
|
|
55
|
+
xml.tag!("ownedMember",
|
|
56
|
+
"xmi:type" => "uml:Class",
|
|
57
|
+
"xmi:id" => klass.ea_guid || klass.object_id.to_s,
|
|
58
|
+
"name" => klass.name || "Anonymous") do
|
|
59
|
+
attrs.each { |attr| emit_attribute(xml, attr) }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def emit_attribute(xml, attr)
|
|
64
|
+
xml.tag!("ownedAttribute",
|
|
65
|
+
"xmi:type" => "uml:Property",
|
|
66
|
+
"xmi:id" => attr.ea_guid || attr.property_id.to_s,
|
|
67
|
+
"name" => attr.name)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def class_attributes(klass)
|
|
71
|
+
attrs = model.collections[:attributes] || []
|
|
72
|
+
attrs.select { |a| a.object_id == klass.object_id }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
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,142 @@
|
|
|
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
|
+
# Uses ClassMapping to translate UML class names to GML types
|
|
11
|
+
# and NamespaceRegistry to declare target namespaces. Walks the
|
|
12
|
+
# model's classifiers and emits `<xs:element>` + `<xs:complexType>`
|
|
13
|
+
# pairs.
|
|
14
|
+
#
|
|
15
|
+
# Both class_mapping and namespaces default to the EA-bundled
|
|
16
|
+
# GML fixtures when present, so callers can do
|
|
17
|
+
# Ea::Export::Xsd::Generator.call(model, target_namespace: ...)
|
|
18
|
+
# without manually loading XML fixtures.
|
|
19
|
+
class Generator
|
|
20
|
+
XSD_NS = "http://www.w3.org/2001/XMLSchema"
|
|
21
|
+
|
|
22
|
+
DEFAULT_MAPPING_PATH = "spec/fixtures/mdg/ea_config/gml/GMLClassMapping.xml".freeze
|
|
23
|
+
DEFAULT_NAMESPACES_PATH = "spec/fixtures/mdg/ea_config/gml/GMLNamespaces.xml".freeze
|
|
24
|
+
|
|
25
|
+
attr_reader :class_mapping, :namespaces
|
|
26
|
+
|
|
27
|
+
def initialize(class_mapping: nil, namespaces: nil)
|
|
28
|
+
@class_mapping = class_mapping || ClassMapping.from_path(DEFAULT_MAPPING_PATH)
|
|
29
|
+
@namespaces = namespaces || NamespaceRegistry.from_path(DEFAULT_NAMESPACES_PATH)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Class-method shortcut. Accepts the same options as #initialize
|
|
33
|
+
# via keyword args.
|
|
34
|
+
def self.call(model, target_namespace: "http://example.com/xsd",
|
|
35
|
+
prefix: "tns", **opts)
|
|
36
|
+
new(**opts).call(model, target_namespace: target_namespace, prefix: prefix)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Generate an XSD schema for the given model.
|
|
40
|
+
# @param model [#collections] anything responding to #collections
|
|
41
|
+
# with a :packages and :objects collection (Ea::Qea::Database).
|
|
42
|
+
# @param target_namespace [String] XSD targetNamespace
|
|
43
|
+
# @param prefix [String] namespace prefix for the target
|
|
44
|
+
# @return [String] serialized XSD
|
|
45
|
+
def call(model, target_namespace:, prefix: "tns")
|
|
46
|
+
doc = build_schema_doc(target_namespace, prefix)
|
|
47
|
+
walk_model(model, doc)
|
|
48
|
+
doc.to_xml(indent: 2)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def build_schema_doc(target_namespace, prefix)
|
|
54
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
|
55
|
+
xml.schema("xmlns" => XSD_NS,
|
|
56
|
+
"xmlns:#{prefix}" => target_namespace,
|
|
57
|
+
"targetNamespace" => target_namespace,
|
|
58
|
+
"elementFormDefault" => "qualified")
|
|
59
|
+
end
|
|
60
|
+
builder.doc
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def walk_model(model, doc)
|
|
64
|
+
classes = model_classes(model)
|
|
65
|
+
classes.each { |klass| emit_class(doc, doc.root, klass) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Extract UML-like classes from the model. Ea::Qea::Database
|
|
69
|
+
# stores them in :objects; we filter to classes (Object_Type
|
|
70
|
+
# == "Class") and emit a struct for the generator's needs.
|
|
71
|
+
Classifier = Struct.new(:name, :stereotype, :attributes,
|
|
72
|
+
keyword_init: true)
|
|
73
|
+
|
|
74
|
+
def model_classes(model)
|
|
75
|
+
objects = model.collections[:objects] || []
|
|
76
|
+
objects.select { |o| o.object_type == "Class" }.map do |obj|
|
|
77
|
+
Classifier.new(
|
|
78
|
+
name: obj.name,
|
|
79
|
+
stereotype: stereotype_for(model, obj),
|
|
80
|
+
attributes: attributes_for(model, obj)
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def stereotype_for(model, obj)
|
|
86
|
+
# Walk t_xref for the object's GUID; pick first @STEREO.
|
|
87
|
+
xrefs = model.collections[:xrefs] || []
|
|
88
|
+
stereo_xref = xrefs.find do |xr|
|
|
89
|
+
xr.client == obj.ea_guid && xr.description&.include?("@STEREO")
|
|
90
|
+
end
|
|
91
|
+
return nil unless stereo_xref
|
|
92
|
+
|
|
93
|
+
match = stereo_xref.description.match(/Name=([^;]+)/)
|
|
94
|
+
match ? match[1] : nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def attributes_for(model, obj)
|
|
98
|
+
attrs = model.collections[:attributes] || []
|
|
99
|
+
attrs.select { |a| a.object_id == obj.object_id }
|
|
100
|
+
.map { |a| { name: a.name, type: a.type || "string" } }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def emit_class(doc, schema_root, classifier)
|
|
104
|
+
mapping = class_mapping.for(classifier.name) ||
|
|
105
|
+
class_mapping.for(classifier.stereotype.to_s)
|
|
106
|
+
|
|
107
|
+
type_name = mapping&.type || "#{classifier.name}Type"
|
|
108
|
+
element_name = mapping&.element || classifier.name&.downcase
|
|
109
|
+
|
|
110
|
+
schema_root.add_child(element_node(doc, element_name, type_name))
|
|
111
|
+
schema_root.add_child(complex_type_node(doc, type_name, classifier))
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def element_node(doc, name, type_name)
|
|
115
|
+
el = doc.create_element("element", "xmlns" => XSD_NS)
|
|
116
|
+
el["name"] = name
|
|
117
|
+
el["type"] = "tns:#{type_name}"
|
|
118
|
+
el
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def complex_type_node(doc, type_name, classifier)
|
|
122
|
+
ct = doc.create_element("complexType", "xmlns" => XSD_NS)
|
|
123
|
+
ct["name"] = type_name
|
|
124
|
+
seq = doc.create_element("sequence", "xmlns" => XSD_NS)
|
|
125
|
+
classifier.attributes.each do |attr|
|
|
126
|
+
seq.add_child(attribute_element(doc, attr))
|
|
127
|
+
end
|
|
128
|
+
ct.add_child(seq)
|
|
129
|
+
ct
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def attribute_element(doc, attr)
|
|
133
|
+
el = doc.create_element("element", "xmlns" => XSD_NS)
|
|
134
|
+
el["name"] = attr[:name].to_s
|
|
135
|
+
el["type"] = "xs:string"
|
|
136
|
+
el["minOccurs"] = "0"
|
|
137
|
+
el
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
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,14 @@
|
|
|
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 :Xmi, "ea/export/xmi"
|
|
9
|
+
autoload :Json, "ea/export/json"
|
|
10
|
+
autoload :JsonSchema, "ea/export/json_schema"
|
|
11
|
+
autoload :PlantUml, "ea/export/plantuml"
|
|
12
|
+
autoload :Xsd, "ea/export/xsd"
|
|
13
|
+
end
|
|
14
|
+
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
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Lint
|
|
5
|
+
module Rules
|
|
6
|
+
# Two classes with the same name in the same package.
|
|
7
|
+
class DuplicateNameRule < LintRule
|
|
8
|
+
self.severity = :warning
|
|
9
|
+
|
|
10
|
+
def check(model)
|
|
11
|
+
classes = (model.collections[:objects] || [])
|
|
12
|
+
.select { |o| o.object_type == "Class" }
|
|
13
|
+
grouped = classes.group_by { |c| [c.package_id, c.name] }
|
|
14
|
+
grouped.flat_map do |(pkg_id, name), group|
|
|
15
|
+
next [] if group.size == 1
|
|
16
|
+
|
|
17
|
+
group.map do |klass|
|
|
18
|
+
offense(entity_id: klass.object_id,
|
|
19
|
+
entity_name: klass.name,
|
|
20
|
+
message: "Duplicate class name '#{name}' in package #{pkg_id} " \
|
|
21
|
+
"(#{group.size} occurrences)")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Lint
|
|
5
|
+
module Rules
|
|
6
|
+
# Packages named "Application Schema" or with `applicationSchema`
|
|
7
|
+
# stereotype should have at least one class with a GML stereotype.
|
|
8
|
+
class MissingStereotypeRule < LintRule
|
|
9
|
+
self.severity = :info
|
|
10
|
+
|
|
11
|
+
GML_STEREOTYPES = %w[FeatureType Type DataType CodeList
|
|
12
|
+
Enumeration ObjectType].freeze
|
|
13
|
+
|
|
14
|
+
def check(model)
|
|
15
|
+
classes = (model.collections[:objects] || [])
|
|
16
|
+
.select { |o| o.object_type == "Class" }
|
|
17
|
+
classes_without_stereo = classes.select { |c| stereotype_refs(c).empty? }
|
|
18
|
+
classes_without_stereo.map do |klass|
|
|
19
|
+
offense(entity_id: klass.object_id,
|
|
20
|
+
entity_name: klass.name,
|
|
21
|
+
message: "Class has no applied stereotype",
|
|
22
|
+
severity: :info)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def stereotype_refs(klass)
|
|
29
|
+
refs = klass.respond_to?(:stereotype_refs) ? klass.stereotype_refs : nil
|
|
30
|
+
refs || []
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|