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.
Files changed (548) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +128 -1
  3. data/Rakefile +3 -0
  4. data/TODO.complete/01-t-xref-parsing.md +91 -0
  5. data/TODO.complete/02-per-label-geometry.md +50 -0
  6. data/TODO.complete/03-object-properties-constraints.md +57 -0
  7. data/TODO.complete/04-operation-params.md +37 -0
  8. data/TODO.complete/05-pdata-flag-matrix.md +52 -0
  9. data/TODO.complete/06-emf-image-rendering.md +34 -0
  10. data/TODO.complete/07-auxiliary-tables.md +47 -0
  11. data/TODO.complete/10-mdg-stereotype-registry.md +40 -0
  12. data/TODO.complete/11-gml-xsd-generation.md +42 -0
  13. data/TODO.complete/13-shapescript-interpreter.md +28 -0
  14. data/TODO.complete/20-cli-diff.md +46 -0
  15. data/TODO.complete/21-cli-render.md +26 -0
  16. data/TODO.complete/22-cli-export.md +29 -0
  17. data/TODO.complete/23-cli-mdg.md +23 -0
  18. data/TODO.complete/30-headerlines-ocp-refactor.md +72 -0
  19. data/TODO.complete/31-public-send-cleanup.md +61 -0
  20. data/TODO.complete/32-spec-coverage.md +33 -0
  21. data/TODO.complete/33-blocked-parity-gaps.md +41 -0
  22. data/TODO.complete/36-stereotype-icon-wiring.md +32 -0
  23. data/TODO.complete/37-cli-lint.md +32 -0
  24. data/TODO.complete/38-cli-query.md +26 -0
  25. data/TODO.complete/39-cli-info.md +23 -0
  26. data/TODO.complete/40-ocl-evaluator.md +31 -0
  27. data/TODO.complete/41-round-trip-spec.md +18 -0
  28. data/TODO.complete/42-html-diff-report.md +18 -0
  29. data/TODO.complete/43-json-schema-export.md +20 -0
  30. data/TODO.complete/44-remaining-qea-tables.md +40 -0
  31. data/TODO.complete/45-shapescript-extensions.md +25 -0
  32. data/TODO.complete/46-xsd-consolidation.md +20 -0
  33. data/TODO.complete/47-headerlines-legacy-removal.md +20 -0
  34. data/TODO.complete/51-remove-respond-to.md +25 -0
  35. data/TODO.complete/52-xsd-decouple-fixtures.md +23 -0
  36. data/TODO.complete/53-xmi-public-send-cleanup.md +26 -0
  37. data/TODO.complete/54-wire-stereotype-icon-renderer.md +20 -0
  38. data/TODO.complete/55-wire-shapescript.md +20 -0
  39. data/TODO.complete/56-wire-emf-renderer.md +27 -0
  40. data/TODO.complete/57-wire-ocl-evaluator.md +23 -0
  41. data/TODO.complete/58-diff-modifications.md +21 -0
  42. data/TODO.complete/59-delete-toy-xmi-export.md +30 -0
  43. data/TODO.complete/60-curate-json-export.md +20 -0
  44. data/TODO.complete/61-plantuml-relationships.md +22 -0
  45. data/TODO.complete/62-changelog.md +17 -0
  46. data/TODO.complete/63-dependabot-fix.md +18 -0
  47. data/TODO.complete/STATUS.md +85 -0
  48. data/TODO.diagrams/01-svg-measurement-harness.md +31 -0
  49. data/TODO.diagrams/02-polygon-over-rendering.md +29 -0
  50. data/TODO.diagrams/03-text-over-rendering.md +30 -0
  51. data/TODO.diagrams/04-rect-under-rendering.md +33 -0
  52. data/TODO.diagrams/05-canvas-dimension-parity.md +51 -0
  53. data/TODO.diagrams/06-group-layering-parity.md +39 -0
  54. data/TODO.diagrams/07-text-content-fidelity.md +65 -0
  55. data/TODO.diagrams/08-qea-direct-pipeline.md +46 -0
  56. data/TODO.diagrams/12-connector-labels.md +40 -0
  57. data/TODO.diagrams/12-per-entity-svg-layer-ordering.md +24 -0
  58. data/TODO.diagrams/13-marker-polygon-vs-path-dispatch.md +20 -0
  59. data/TODO.diagrams/13-per-text-type-font-sizes.md +33 -0
  60. data/TODO.diagrams/14-center-header-text.md +33 -0
  61. data/TODO.diagrams/14-filter-diagram-frame-elements.md +18 -0
  62. data/TODO.diagrams/15-deep-dead-code-audit.md +47 -0
  63. data/TODO.diagrams/15-per-element-font-from-style.md +24 -0
  64. data/TODO.diagrams/16-canvas-marker-extent.md +30 -0
  65. data/TODO.diagrams/16-document-remaining-parity-gaps.md +91 -0
  66. data/TODO.diagrams/17-italic-abstract-classes.md +28 -0
  67. data/TODO.diagrams/17-remaining-parity-gaps.md +90 -0
  68. data/TODO.diagrams/18-canvas-from-image-bounds.md +20 -0
  69. data/TODO.diagrams/18-property-lookup-index.md +42 -0
  70. data/TODO.diagrams/19-connector-sx-sy-routing.md +27 -0
  71. data/TODO.diagrams/19-replace-respond-to-with-is-a.md +31 -0
  72. data/TODO.diagrams/20-split-labels-emitter.md +56 -0
  73. data/TODO.diagrams/20-visibility-glyph-font-encoding.md +27 -0
  74. data/TODO.diagrams/21-aggregation-navigability-arrow.md +21 -0
  75. data/TODO.diagrams/21-auto-generated-legend-block.md +65 -0
  76. data/TODO.diagrams/22-ghost-element-discriminator.md +57 -0
  77. data/TODO.diagrams/22-style-based-path-grouping.md +28 -0
  78. data/TODO.diagrams/23-marker-polygon-deduplication.md +47 -0
  79. data/TODO.diagrams/23-multiplicity-label-positioning.md +29 -0
  80. data/TODO.diagrams/24-marker-anchor-structural.md +35 -0
  81. data/TODO.diagrams/24-track-marker-anchors-structurally.md +66 -0
  82. data/TODO.diagrams/24-truncate-properties-to-fit-box.md +24 -0
  83. data/TODO.diagrams/26-phantom-general-parent-rendering.md +59 -0
  84. data/TODO.diagrams/27-path-grouping-mode-detection.md +43 -0
  85. data/TODO.diagrams/28-architecture-cleanup-batch.md +47 -0
  86. data/TODO.diagrams/30-decompose-elements-groups-for.md +52 -0
  87. data/TODO.diagrams/32-examples-coverage-batch.md +78 -0
  88. data/TODO.diagrams/36-instance-specification-model.md +42 -0
  89. data/TODO.diagrams/37-element-icon-decorations.md +41 -0
  90. data/TODO.diagrams/38-html-note-and-receptions.md +32 -0
  91. data/TODO.diagrams/39-property-stereotype-discriminator.md +40 -0
  92. data/TODO.diagrams/40-standalone-1-discriminator.md +38 -0
  93. data/TODO.diagrams/41-xmi-to-svg-pipeline.md +113 -0
  94. data/TODO.diagrams/42-close-all-parity-deltas.md +69 -0
  95. data/TODO.diagrams/43-marker-deduplication-strategy.md +54 -0
  96. data/TODO.diagrams/44-element-icon-decorations.md +34 -0
  97. data/TODO.diagrams/45-phantom-parent-class-boxes.md +40 -0
  98. data/TODO.diagrams/46-user-drawn-legend-swatches.md +44 -0
  99. data/TODO.diagrams/47-attribute-line-fidelity.md +72 -0
  100. data/TODO.diagrams/48-spec-coverage-critical-emitter-components.md +45 -0
  101. data/TODO.diagrams/49-spec-coverage-orchestrators.md +25 -0
  102. data/TODO.diagrams/50-examples-parity.md +85 -0
  103. data/TODO.diagrams/51-property-oy-gap-and-remaining-gaps.md +48 -0
  104. data/TODO.diagrams/52-marker-registration-architecture.md +89 -0
  105. data/TODO.diagrams/53-remaining-example-gaps.md +86 -0
  106. data/TODO.diagrams/54-display-name-nested-classes.md +40 -0
  107. data/TODO.diagrams/55-visibility-icon-discriminator.md +58 -0
  108. data/TODO.diagrams/56-package-contents-rendering.md +148 -0
  109. data/TODO.diagrams/57-instance-specification-model.md +100 -0
  110. data/TODO.diagrams/58-domain-model-note-text.md +67 -0
  111. data/TODO.diagrams/59-classes-arrow-discriminator.md +57 -0
  112. data/TODO.diagrams/60-testschema-association-marker.md +40 -0
  113. data/TODO.diagrams/61-legend-block-rendering.md +55 -0
  114. data/TODO.diagrams/61-plateau-parity-gaps.md +59 -0
  115. data/TODO.diagrams/62-ghost-element-rendering.md +56 -0
  116. data/TODO.diagrams/63-qualified-name-wrap.md +36 -0
  117. data/TODO.diagrams/64-property-stereotype-labels.md +22 -0
  118. data/TODO.diagrams/65-path-coordinate-frame.md +62 -0
  119. data/TODO.diagrams/67-path-coordinate-frame.md +62 -0
  120. data/TODO.diagrams/68-inherited-attributes.md +65 -0
  121. data/TODO.diagrams/69-package-anchor-conditional.md +29 -0
  122. data/TODO.diagrams/70-mdg-technology-support.md +68 -0
  123. data/TODO.diagrams/71-off-canvas-parent-ghost.md +45 -0
  124. data/TODO.diagrams/72-basic-attr-divider-paths.md +39 -0
  125. data/TODO.diagrams/73-plateau-polygon-gap.md +69 -0
  126. data/TODO.diagrams/74-plateau-rect-gap.md +22 -0
  127. data/TODO.diagrams/75-plateau-text-gap.md +39 -0
  128. data/TODO.diagrams/76-basic-poly-path-gaps.md +41 -0
  129. data/TODO.diagrams/77-basic-text-overrender.md +40 -0
  130. data/TODO.diagrams/78-headerlines-ocp-refactor.md +65 -0
  131. data/TODO.diagrams/79-architecture-backlog.md +65 -0
  132. data/TODO.diagrams/80-basic-triangle-polygons.md +51 -0
  133. data/TODO.diagrams/81-plateau-path-overrender.md +53 -0
  134. data/TODO.diagrams/82-appearance-canvas-offset.md +54 -0
  135. data/TODO.diagrams/83-3d-diagram-subtitle.md +58 -0
  136. data/TODO.diagrams/84-canvas-offset-mismatches.md +61 -0
  137. data/TODO.diagrams/85-phantom-property-overrender.md +95 -0
  138. data/TODO.diagrams/86-foreign-package-rule.md +62 -0
  139. data/TODO.diagrams/87-internal-tech-encryption.md +80 -0
  140. data/TODO.diagrams/EXAMPLES_STATUS.md +88 -0
  141. data/TODO.diagrams/README.md +69 -0
  142. data/TODO.diagrams/STATUS.md +73 -0
  143. data/TODO.fixup/01-default-grouped-paths.md +34 -0
  144. data/TODO.fixup/02-marker-consolidation-per-connector.md +20 -0
  145. data/TODO.fixup/03-diagram-font-inference.md +28 -0
  146. data/TODO.fixup/04-extract-bounds-calculator.md +25 -0
  147. data/TODO.fixup/05-marker-registry-ocp.md +32 -0
  148. data/TODO.fixup/06-connector-path-bend-routing.md +28 -0
  149. data/TODO.fixup/07-edge-attachment-formula.md +22 -0
  150. data/TODO.fixup/08-visual-diff-harness.md +28 -0
  151. data/TODO.fixup/09-extract-element-compartments.md +34 -0
  152. data/TODO.fixup/10-canvas-width-overshoot-fix.md +20 -0
  153. data/TODO.fixup/11-layer-sequencer-extraction.md +18 -0
  154. data/TODO.fixup/12-path-style-harmony.md +23 -0
  155. data/TODO.fixup/13-datatype-stereotype-fallback.md +28 -0
  156. data/TODO.fixup/14-ea-exe-reverse-engineering-notes.md +56 -0
  157. data/TODO.fixup/15-enumeration-literal-rendering.md +30 -0
  158. data/TODO.fixup/16-operation-compartment-rendering.md +42 -0
  159. data/TODO.fixup/17-path-count-calibration.md +27 -0
  160. data/TODO.fixup/18-polygon-count-calibration.md +32 -0
  161. data/TODO.fixup/19-rect-count-undershoot-investigation.md +40 -0
  162. data/TODO.fixup/20-stereotype-color-from-classifier.md +18 -0
  163. data/TODO.fixup/21-visibility-filter-for-properties.md +24 -0
  164. data/TODO.fixup/22-multiplicity-display-rule-calibration.md +25 -0
  165. data/TODO.fixup/23-diagram-frame-title-element.md +31 -0
  166. data/TODO.fixup/24-note-element-rendering.md +23 -0
  167. data/TODO.fixup/25-ea-exe-disassembly-findings.md +177 -0
  168. data/TODO.fixup/26-svg-renderer-architecture.md +78 -0
  169. data/TODO.fixup/27-svg-element-emitters-disassembly.md +135 -0
  170. data/TODO.fixup/28-geometry-parser-identification.md +57 -0
  171. data/TODO.fixup/29-qea-svg-xmi-triple-comparison.md +185 -0
  172. data/TODO.fixup/30-theme-system-stub.md +46 -0
  173. data/TODO.fixup/31-theme-value-object-registry.md +17 -0
  174. data/TODO.fixup/32-styleex-on-diagram-model.md +22 -0
  175. data/TODO.fixup/33-text-encoding-decimals-rotation.md +29 -0
  176. data/TODO.fixup/34-per-type-fill-via-theme.md +18 -0
  177. data/TODO.fixup/35-stroke-width-per-theme.md +7 -0
  178. data/TODO.fixup/36-qea-rect-coord-normalization.md +24 -0
  179. data/TODO.fixup/37-package-shape-rendering.md +22 -0
  180. data/TODO.fixup/38-note-element-rendering.md +15 -0
  181. data/TODO.fixup/39-styleex-flag-honoring.md +25 -0
  182. data/TODO.fixup/40-connector-dashed-lines.md +15 -0
  183. data/TODO.fixup/41-diagram-frame-showborder-detection.md +23 -0
  184. data/TODO.fixup/42-textrenderer-centralization.md +10 -0
  185. data/TODO.fixup/43-colorresolver-unification.md +24 -0
  186. data/TODO.fixup/44-theme-aware-textrenderer.md +17 -0
  187. data/TODO.fixup/45-theme-aware-fontresolver.md +22 -0
  188. data/TODO.fixup/46-style-parameterized-by-theme.md +21 -0
  189. data/TODO.fixup/47-canvas-frame-inset.md +19 -0
  190. data/TODO.fixup/48-background-rect-format.md +15 -0
  191. data/TODO.fixup/49-all-samples-integration-test.md +17 -0
  192. data/TODO.fixup/50-svg-emitter-architecture-docs.md +33 -0
  193. data/TODO.fixup/51-promote-theme-to-ea-namespace.md +28 -0
  194. data/TODO.fixup/52-theme-definition-integrated-fills.md +26 -0
  195. data/TODO.fixup/53-theme-registry-yaml-loading.md +50 -0
  196. data/TODO.fixup/54-diagram-theme-api.md +50 -0
  197. data/TODO.fixup/55-update-eaemitter-to-ea-theme.md +23 -0
  198. data/TODO.fixup/56-theme-yaml-files.md +65 -0
  199. data/TODO.fixup/57-theme-specs-and-integration.md +27 -0
  200. data/TODO.fixup/58-ea-entry-point-theme-autoload.md +20 -0
  201. data/TODO.fixup/59-thread-theme-through-all-renderers.md +29 -0
  202. data/TODO.fixup/60-cli-theme-option.md +22 -0
  203. data/TODO.fixup/61-theme-validation.md +28 -0
  204. data/TODO.fixup/62-theme-builder-dsl.md +31 -0
  205. data/TODO.fixup/63-theme-integration-rendering-spec.md +24 -0
  206. data/TODO.fixup/64-theme-writeback-styleex.md +21 -0
  207. data/TODO.fixup/65-remove-dead-eaemitter-theme-files.md +30 -0
  208. data/TODO.fixup/66-styleex-writeback.md +22 -0
  209. data/TODO.fixup/67-delete-dead-code.md +28 -0
  210. data/TODO.fixup/68-figure-parity-status.md +82 -0
  211. data/TODO.fixup/69-package-polygon-rendering.md +54 -0
  212. data/TODO.fixup/70-tagged-values-compartment.md +39 -0
  213. data/TODO.fixup/71-auto-detect-theme-from-xmi.md +37 -0
  214. data/TODO.fixup/72-sample-exports-as-regression-specs.md +37 -0
  215. data/TODO.fixup/73-delete-dead-transformations.md +14 -0
  216. data/TODO.fixup/74-delete-dead-diagram-renderers.md +18 -0
  217. data/TODO.fixup/75-visibility-legend-icons.md +23 -0
  218. data/TODO.fixup/76-plateau-text-undershoot.md +27 -0
  219. data/TODO.fixup/77-canvas-frame-margins.md +28 -0
  220. data/TODO.fixup/78-note-element-rendering.md +30 -0
  221. data/TODO.fixup/79-operation-compartment-rendering.md +27 -0
  222. data/TODO.fixup/80-tagged-value-stereotype-visibility.md +37 -0
  223. data/TODO.fixup/81-figure-parity-current-state.md +63 -0
  224. data/config/qea_schema.yml +356 -0
  225. data/config/themes/119.yml +98 -0
  226. data/config/themes/default.yml +14 -0
  227. data/examples/exports/20251010_current_plateau_v5/UML_EA.DTD +6856 -0
  228. data/examples/exports/20251010_current_plateau_v5/model.xml +96709 -0
  229. data/examples/exports/ArcGISWorkspace_template/UML_EA.DTD +6856 -0
  230. data/examples/exports/ArcGISWorkspace_template/model.xml +1427 -0
  231. data/examples/exports/UmlModel_template/UML_EA.DTD +6856 -0
  232. data/examples/exports/UmlModel_template/model.xml +23 -0
  233. data/examples/exports/basic/UML_EA.DTD +6856 -0
  234. data/examples/exports/basic/model.xml +8039 -0
  235. data/examples/exports/simple/UML_EA.DTD +6856 -0
  236. data/examples/exports/simple/model.xml +585 -0
  237. data/examples/exports/simple_example/UML_EA.DTD +6856 -0
  238. data/examples/exports/simple_example/model.xml +23 -0
  239. data/examples/exports/test/UML_EA.DTD +6856 -0
  240. data/examples/exports/test/model.xml +1638 -0
  241. data/examples/qea/basic.qea +0 -0
  242. data/frontend/README.md +51 -0
  243. data/frontend/dist/app.iife.js +24 -0
  244. data/frontend/dist/style.css +1 -0
  245. data/frontend/index.html +26 -0
  246. data/frontend/package-lock.json +2359 -0
  247. data/frontend/package.json +26 -0
  248. data/frontend/src/App.vue +59 -0
  249. data/frontend/src/app.ts +8 -0
  250. data/frontend/src/components/AppHeader.vue +127 -0
  251. data/frontend/src/components/AppSidebar.vue +119 -0
  252. data/frontend/src/components/ClassDetails.vue +247 -0
  253. data/frontend/src/components/DiagramView.vue +79 -0
  254. data/frontend/src/components/PackageDetails.vue +92 -0
  255. data/frontend/src/components/PackageTreeNode.vue +64 -0
  256. data/frontend/src/components/SearchResults.vue +48 -0
  257. data/frontend/src/components/WelcomeView.vue +87 -0
  258. data/frontend/src/shims-vue.d.ts +5 -0
  259. data/frontend/src/stores/dataStore.ts +79 -0
  260. data/frontend/src/stores/uiStore.ts +167 -0
  261. data/frontend/src/styles/main.css +1566 -0
  262. data/frontend/src/types.ts +221 -0
  263. data/frontend/tests/e2e/__screenshots__/new-dark-mode.png +0 -0
  264. data/frontend/tests/e2e/__screenshots__/new-package-details.png +0 -0
  265. data/frontend/tests/e2e/__screenshots__/new-search-modal.png +0 -0
  266. data/frontend/tests/e2e/__screenshots__/new-sidebar-collapsed.png +0 -0
  267. data/frontend/tests/e2e/__screenshots__/new-welcome.png +0 -0
  268. data/frontend/tests/e2e/render.spec.ts +188 -0
  269. data/frontend/tsconfig.json +20 -0
  270. data/frontend/vite.config.ts +26 -0
  271. data/frontend/vitest.config.ts +8 -0
  272. data/lib/ea/cli/app.rb +57 -2
  273. data/lib/ea/cli/command/diagrams.rb +37 -28
  274. data/lib/ea/cli/command/diff.rb +46 -0
  275. data/lib/ea/cli/command/export.rb +101 -0
  276. data/lib/ea/cli/command/info.rb +75 -0
  277. data/lib/ea/cli/command/lint.rb +48 -0
  278. data/lib/ea/cli/command/mdg.rb +98 -0
  279. data/lib/ea/cli/command/query.rb +34 -0
  280. data/lib/ea/cli/command/render.rb +135 -0
  281. data/lib/ea/cli/command/svg.rb +8 -15
  282. data/lib/ea/cli/command.rb +7 -0
  283. data/lib/ea/diagram/display_config.rb +184 -0
  284. data/lib/ea/diagram.rb +4 -41
  285. data/lib/ea/diff/change.rb +30 -0
  286. data/lib/ea/diff/comparator.rb +109 -0
  287. data/lib/ea/diff/html_reporter.rb +91 -0
  288. data/lib/ea/diff.rb +15 -0
  289. data/lib/ea/export/json/generator.rb +118 -0
  290. data/lib/ea/export/json.rb +12 -0
  291. data/lib/ea/export/json_schema/generator.rb +77 -0
  292. data/lib/ea/export/json_schema.rb +12 -0
  293. data/lib/ea/export/plantuml/generator.rb +140 -0
  294. data/lib/ea/export/plantuml.rb +12 -0
  295. data/lib/ea/export/xsd/class_mapping.rb +65 -0
  296. data/lib/ea/export/xsd/generator.rb +137 -0
  297. data/lib/ea/export/xsd/namespace_registry.rb +82 -0
  298. data/lib/ea/export/xsd.rb +19 -0
  299. data/lib/ea/export.rb +13 -0
  300. data/lib/ea/image/emf_renderer.rb +42 -0
  301. data/lib/ea/image.rb +12 -0
  302. data/lib/ea/lint/engine.rb +30 -0
  303. data/lib/ea/lint/lint_rule.rb +38 -0
  304. data/lib/ea/lint/offense.rb +18 -0
  305. data/lib/ea/lint/rules/cyclic_generalization.rb +62 -0
  306. data/lib/ea/lint/rules/duplicate_name.rb +28 -0
  307. data/lib/ea/lint/rules/missing_stereotype.rb +40 -0
  308. data/lib/ea/lint/rules/naming_convention.rb +44 -0
  309. data/lib/ea/lint/rules/orphan_element.rb +28 -0
  310. data/lib/ea/lint/rules.rb +14 -0
  311. data/lib/ea/lint.rb +13 -0
  312. data/lib/ea/mdg/document.rb +120 -0
  313. data/lib/ea/mdg/loader.rb +118 -0
  314. data/lib/ea/mdg/registry.rb +98 -0
  315. data/lib/ea/mdg/stereotype_alias_registry.rb +77 -0
  316. data/lib/ea/mdg/xml/applies_to.rb +18 -0
  317. data/lib/ea/mdg/xml/apply.rb +18 -0
  318. data/lib/ea/mdg/xml/content.rb +18 -0
  319. data/lib/ea/mdg/xml/documentation.rb +24 -0
  320. data/lib/ea/mdg/xml/stereotype.rb +28 -0
  321. data/lib/ea/mdg/xml/stereotypes.rb +18 -0
  322. data/lib/ea/mdg/xml/tag.rb +28 -0
  323. data/lib/ea/mdg/xml/tagged_values.rb +18 -0
  324. data/lib/ea/mdg/xml/technology.rb +28 -0
  325. data/lib/ea/mdg/xml/uml_profile.rb +22 -0
  326. data/lib/ea/mdg/xml/uml_profiles.rb +18 -0
  327. data/lib/ea/mdg/xml.rb +24 -0
  328. data/lib/ea/mdg.rb +25 -0
  329. data/lib/ea/model/classifier.rb +4 -0
  330. data/lib/ea/model/constraint.rb +24 -0
  331. data/lib/ea/model/diagram.rb +85 -0
  332. data/lib/ea/model/diagram_connector.rb +34 -0
  333. data/lib/ea/model/diagram_element.rb +16 -0
  334. data/lib/ea/model/document.rb +47 -2
  335. data/lib/ea/model/ghost_label.rb +32 -0
  336. data/lib/ea/model/instance_specification.rb +34 -0
  337. data/lib/ea/model/legend.rb +43 -0
  338. data/lib/ea/model/legend_item.rb +26 -0
  339. data/lib/ea/model/note.rb +28 -0
  340. data/lib/ea/model/operation.rb +2 -0
  341. data/lib/ea/model/property.rb +2 -0
  342. data/lib/ea/model/relationship.rb +2 -0
  343. data/lib/ea/model/signal.rb +14 -0
  344. data/lib/ea/model/slot.rb +19 -0
  345. data/lib/ea/model.rb +8 -0
  346. data/lib/ea/ocl/evaluator.rb +94 -0
  347. data/lib/ea/ocl/nodes.rb +30 -0
  348. data/lib/ea/ocl/parser.rb +133 -0
  349. data/lib/ea/ocl/unsupported_error.rb +7 -0
  350. data/lib/ea/ocl.rb +13 -0
  351. data/lib/ea/qea/factory/document_builder.rb +24 -10
  352. data/lib/ea/qea/factory/generalization_builder.rb +9 -11
  353. data/lib/ea/qea/infrastructure/table_reader.rb +9 -0
  354. data/lib/ea/qea/models/ea_author.rb +35 -0
  355. data/lib/ea/qea/models/ea_glossary.rb +34 -0
  356. data/lib/ea/qea/models/ea_image.rb +53 -0
  357. data/lib/ea/qea/models/ea_implement.rb +32 -0
  358. data/lib/ea/qea/models/ea_list.rb +42 -0
  359. data/lib/ea/qea/models/ea_object_effort.rb +34 -0
  360. data/lib/ea/qea/models/ea_object_problem.rb +37 -0
  361. data/lib/ea/qea/models/ea_object_require.rb +34 -0
  362. data/lib/ea/qea/models/ea_object_resource.rb +34 -0
  363. data/lib/ea/qea/models/ea_object_risk.rb +34 -0
  364. data/lib/ea/qea/models/ea_object_scenario.rb +34 -0
  365. data/lib/ea/qea/models/ea_object_test.rb +35 -0
  366. data/lib/ea/qea/models/ea_object_trx.rb +34 -0
  367. data/lib/ea/qea/models/ea_operation.rb +7 -0
  368. data/lib/ea/qea/models/ea_palette.rb +30 -0
  369. data/lib/ea/qea/models/ea_palette_item.rb +36 -0
  370. data/lib/ea/qea/models/ea_phase.rb +40 -0
  371. data/lib/ea/qea/models/ea_role_constraint.rb +32 -0
  372. data/lib/ea/qea/models/ea_secrypt.rb +34 -0
  373. data/lib/ea/qea/models/ea_version.rb +39 -0
  374. data/lib/ea/qea/models/ea_xref.rb +11 -1
  375. data/lib/ea/qea/models.rb +19 -0
  376. data/lib/ea/qea/services/database_loader.rb +22 -1
  377. data/lib/ea/qea/validation/database/ocl_constraint_validator.rb +65 -0
  378. data/lib/ea/qea/validation/database.rb +2 -0
  379. data/lib/ea/qea/validation/validation_engine.rb +2 -0
  380. data/lib/ea/qea/validation.rb +2 -0
  381. data/lib/ea/query/builder.rb +127 -0
  382. data/lib/ea/query.rb +9 -0
  383. data/lib/ea/render/image_converter.rb +101 -0
  384. data/lib/ea/render.rb +10 -0
  385. data/lib/ea/shapescript/parser.rb +208 -0
  386. data/lib/ea/shapescript/renderer.rb +74 -0
  387. data/lib/ea/shapescript/shape.rb +34 -0
  388. data/lib/ea/shapescript.rb +24 -0
  389. data/lib/ea/sources/qea/adapter.rb +22 -5
  390. data/lib/ea/sources/qea/classifier_builder.rb +184 -7
  391. data/lib/ea/sources/qea/diagram_builder.rb +352 -24
  392. data/lib/ea/sources/qea/id_normalizer.rb +16 -1
  393. data/lib/ea/sources/qea/instance_builder.rb +113 -0
  394. data/lib/ea/sources/qea/legend_builder.rb +137 -0
  395. data/lib/ea/sources/qea/note_builder.rb +58 -0
  396. data/lib/ea/sources/qea/object_classifier_map.rb +6 -1
  397. data/lib/ea/sources/qea/operation_builder.rb +1 -0
  398. data/lib/ea/sources/qea/package_builder.rb +16 -0
  399. data/lib/ea/sources/qea/relationship_builder.rb +21 -5
  400. data/lib/ea/sources/qea/tagged_value_builder.rb +28 -9
  401. data/lib/ea/sources/qea/xref/legend_definition.rb +35 -0
  402. data/lib/ea/sources/qea/xref/parser.rb +150 -0
  403. data/lib/ea/sources/qea/xref/property_definition.rb +29 -0
  404. data/lib/ea/sources/qea/xref/record.rb +41 -0
  405. data/lib/ea/sources/qea/xref/stereotype_application.rb +37 -0
  406. data/lib/ea/sources/qea/xref.rb +27 -0
  407. data/lib/ea/sources/qea.rb +4 -0
  408. data/lib/ea/sources/xmi/adapter.rb +80 -2
  409. data/lib/ea/sources/xmi/classifier_builder.rb +10 -1
  410. data/lib/ea/sources/xmi/diagram_builder.rb +95 -18
  411. data/lib/ea/sources/xmi/extension_geometry_parser.rb +19 -2
  412. data/lib/ea/sources/xmi/extension_style_parser.rb +15 -2
  413. data/lib/ea/sources/xmi/note_builder.rb +46 -0
  414. data/lib/ea/sources/xmi/property_builder.rb +9 -0
  415. data/lib/ea/sources/xmi/stereotype_builder.rb +54 -0
  416. data/lib/ea/sources/xmi/tag_builder.rb +45 -0
  417. data/lib/ea/sources/xmi/umldi_keyword_extractor.rb +87 -0
  418. data/lib/ea/sources/xmi.rb +4 -0
  419. data/lib/ea/spa/output/sharded_multi_file_strategy.rb +18 -3
  420. data/lib/ea/spa/output/single_file_strategy.rb +8 -2
  421. data/lib/ea/spa/output/strategy.rb +34 -0
  422. data/lib/ea/svg/connector_router.rb +134 -0
  423. data/lib/ea/svg/ea_emitter/background.rb +2 -3
  424. data/lib/ea/svg/ea_emitter/bounds_calculator.rb +142 -0
  425. data/lib/ea/svg/ea_emitter/canvas.rb +56 -33
  426. data/lib/ea/svg/ea_emitter/color_resolver.rb +56 -0
  427. data/lib/ea/svg/ea_emitter/compartment/attributes.rb +28 -0
  428. data/lib/ea/svg/ea_emitter/compartment/constraints.rb +30 -0
  429. data/lib/ea/svg/ea_emitter/compartment/enum_literals.rb +86 -0
  430. data/lib/ea/svg/ea_emitter/compartment/header.rb +32 -0
  431. data/lib/ea/svg/ea_emitter/compartment/header_divider.rb +57 -0
  432. data/lib/ea/svg/ea_emitter/compartment/instance_slots.rb +96 -0
  433. data/lib/ea/svg/ea_emitter/compartment/instance_underline.rb +67 -0
  434. data/lib/ea/svg/ea_emitter/compartment/note_body.rb +52 -0
  435. data/lib/ea/svg/ea_emitter/compartment/note_text.rb +102 -0
  436. data/lib/ea/svg/ea_emitter/compartment/operations.rb +38 -0
  437. data/lib/ea/svg/ea_emitter/compartment/package_contents.rb +72 -0
  438. data/lib/ea/svg/ea_emitter/compartment/package_from_parent.rb +136 -0
  439. data/lib/ea/svg/ea_emitter/compartment/shape.rb +82 -0
  440. data/lib/ea/svg/ea_emitter/compartment/stereotype_icon.rb +32 -0
  441. data/lib/ea/svg/ea_emitter/compartment/tagged_values.rb +30 -0
  442. data/lib/ea/svg/ea_emitter/compartment.rb +60 -0
  443. data/lib/ea/svg/ea_emitter/connectors.rb +46 -12
  444. data/lib/ea/svg/ea_emitter/diagram_frame.rb +143 -0
  445. data/lib/ea/svg/ea_emitter/document.rb +49 -25
  446. data/lib/ea/svg/ea_emitter/element/attribute_line_builder.rb +117 -0
  447. data/lib/ea/svg/ea_emitter/element/attribute_renderer.rb +119 -0
  448. data/lib/ea/svg/ea_emitter/element/bcol_decoder.rb +29 -0
  449. data/lib/ea/svg/ea_emitter/element/compartment_geometry.rb +113 -0
  450. data/lib/ea/svg/ea_emitter/element/constraint_renderer.rb +53 -0
  451. data/lib/ea/svg/ea_emitter/element/divider_renderer.rb +17 -0
  452. data/lib/ea/svg/ea_emitter/element/enumeration_literal_renderer.rb +34 -0
  453. data/lib/ea/svg/ea_emitter/element/filter.rb +44 -0
  454. data/lib/ea/svg/ea_emitter/element/hand_draw_shape_renderer.rb +77 -0
  455. data/lib/ea/svg/ea_emitter/element/header_line_pipeline.rb +43 -0
  456. data/lib/ea/svg/ea_emitter/element/header_line_provider/instance_spec.rb +27 -0
  457. data/lib/ea/svg/ea_emitter/element/header_line_provider/name.rb +76 -0
  458. data/lib/ea/svg/ea_emitter/element/header_line_provider/parent_ghost.rb +25 -0
  459. data/lib/ea/svg/ea_emitter/element/header_line_provider/stereotype_label.rb +68 -0
  460. data/lib/ea/svg/ea_emitter/element/header_line_provider.rb +19 -0
  461. data/lib/ea/svg/ea_emitter/element/header_lines.rb +27 -0
  462. data/lib/ea/svg/ea_emitter/element/header_renderer.rb +48 -0
  463. data/lib/ea/svg/ea_emitter/element/icon_renderer.rb +109 -0
  464. data/lib/ea/svg/ea_emitter/element/legend_renderer.rb +177 -0
  465. data/lib/ea/svg/ea_emitter/element/note_shape_renderer.rb +112 -0
  466. data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +100 -0
  467. data/lib/ea/svg/ea_emitter/element/package_shape_renderer.rb +181 -0
  468. data/lib/ea/svg/ea_emitter/element/row_icon_renderer.rb +156 -0
  469. data/lib/ea/svg/ea_emitter/element/shape_renderer.rb +17 -0
  470. data/lib/ea/svg/ea_emitter/element/stereotype_icon_renderer.rb +119 -0
  471. data/lib/ea/svg/ea_emitter/element/tagged_value_renderer.rb +50 -0
  472. data/lib/ea/svg/ea_emitter/element/text_escape.rb +20 -0
  473. data/lib/ea/svg/ea_emitter/element.rb +37 -0
  474. data/lib/ea/svg/ea_emitter/elements.rb +283 -95
  475. data/lib/ea/svg/ea_emitter/font_resolver.rb +111 -0
  476. data/lib/ea/svg/ea_emitter/ghost_labels.rb +66 -0
  477. data/lib/ea/svg/ea_emitter/label/end_label.rb +266 -0
  478. data/lib/ea/svg/ea_emitter/label/midpoint_label.rb +101 -0
  479. data/lib/ea/svg/ea_emitter/label/registry.rb +50 -0
  480. data/lib/ea/svg/ea_emitter/label.rb +23 -0
  481. data/lib/ea/svg/ea_emitter/labels.rb +93 -27
  482. data/lib/ea/svg/ea_emitter/layer.rb +20 -0
  483. data/lib/ea/svg/ea_emitter/layer_sequencer.rb +93 -0
  484. data/lib/ea/svg/ea_emitter/marker/arrow_path.rb +52 -0
  485. data/lib/ea/svg/ea_emitter/marker/diamond.rb +57 -0
  486. data/lib/ea/svg/ea_emitter/marker/kind.rb +35 -0
  487. data/lib/ea/svg/ea_emitter/marker/open_triangle.rb +27 -0
  488. data/lib/ea/svg/ea_emitter/marker/package_import.rb +39 -0
  489. data/lib/ea/svg/ea_emitter/marker/plus.rb +40 -0
  490. data/lib/ea/svg/ea_emitter/marker/registry.rb +42 -0
  491. data/lib/ea/svg/ea_emitter/marker.rb +37 -0
  492. data/lib/ea/svg/ea_emitter/markers.rb +351 -64
  493. data/lib/ea/svg/ea_emitter/render_context.rb +52 -0
  494. data/lib/ea/svg/ea_emitter/style.rb +15 -0
  495. data/lib/ea/svg/ea_emitter/text_renderer.rb +92 -0
  496. data/lib/ea/svg/ea_emitter.rb +33 -11
  497. data/lib/ea/svg/parity/checker.rb +105 -0
  498. data/lib/ea/svg/parity/source.rb +59 -0
  499. data/lib/ea/svg/parity/suite.rb +113 -0
  500. data/lib/ea/svg/parity.rb +14 -0
  501. data/lib/ea/svg.rb +2 -15
  502. data/lib/ea/theme/definition.rb +176 -0
  503. data/lib/ea/theme/loader.rb +125 -0
  504. data/lib/ea/theme/registry.rb +96 -0
  505. data/lib/ea/theme.rb +37 -0
  506. data/lib/ea/transformations.rb +5 -88
  507. data/lib/ea/validation/xmi_parity.rb +80 -0
  508. data/lib/ea/validation.rb +12 -0
  509. data/lib/ea/version.rb +4 -1
  510. data/lib/ea.rb +15 -4
  511. data/lib/tasks/svg_bench.rake +87 -0
  512. data/lutaml-uml-0.5.1.gem +0 -0
  513. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes-list.png +0 -0
  514. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes.png +0 -0
  515. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-2-gradients.png +0 -0
  516. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-3-colors.png +0 -0
  517. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-4-appearance-fonts.png +0 -0
  518. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-4-appearance.png +0 -0
  519. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-5-behavior.png +0 -0
  520. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-6-sequence.png +0 -0
  521. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram.png +0 -0
  522. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-2-objects.png +0 -0
  523. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-3-links.png +0 -0
  524. metadata +464 -26
  525. data/lib/ea/diagram/configuration.rb +0 -379
  526. data/lib/ea/diagram/element_renderers/base_renderer.rb +0 -77
  527. data/lib/ea/diagram/element_renderers/class_renderer.rb +0 -323
  528. data/lib/ea/diagram/element_renderers/connector_renderer.rb +0 -41
  529. data/lib/ea/diagram/element_renderers/package_renderer.rb +0 -61
  530. data/lib/ea/diagram/element_renderers.rb +0 -43
  531. data/lib/ea/diagram/extractor.rb +0 -566
  532. data/lib/ea/diagram/layout_engine.rb +0 -170
  533. data/lib/ea/diagram/path_builder.rb +0 -202
  534. data/lib/ea/diagram/style_parser.rb +0 -42
  535. data/lib/ea/diagram/style_resolver.rb +0 -291
  536. data/lib/ea/diagram/svg_renderer.rb +0 -274
  537. data/lib/ea/diagram/util.rb +0 -73
  538. data/lib/ea/svg/bounds_calculator.rb +0 -74
  539. data/lib/ea/svg/connector_path.rb +0 -138
  540. data/lib/ea/svg/element_box.rb +0 -256
  541. data/lib/ea/svg/renderer.rb +0 -77
  542. data/lib/ea/svg/style_resolver.rb +0 -72
  543. data/lib/ea/transformations/configuration.rb +0 -333
  544. data/lib/ea/transformations/format_registry.rb +0 -366
  545. data/lib/ea/transformations/parsers/base_parser.rb +0 -482
  546. data/lib/ea/transformations/parsers/qea_parser.rb +0 -401
  547. data/lib/ea/transformations/parsers/xmi_parser.rb +0 -243
  548. data/lib/ea/transformations/transformation_engine.rb +0 -390
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Ea
6
+ module Export
7
+ # JSON Schema (draft 2020-12) export namespace.
8
+ module JsonSchema
9
+ autoload :Generator, "ea/export/json_schema/generator"
10
+ end
11
+ end
12
+ end
@@ -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