ea 0.3.1 → 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.
Files changed (544) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +3 -0
  3. data/TODO.complete/01-t-xref-parsing.md +91 -0
  4. data/TODO.complete/02-per-label-geometry.md +50 -0
  5. data/TODO.complete/03-object-properties-constraints.md +57 -0
  6. data/TODO.complete/04-operation-params.md +37 -0
  7. data/TODO.complete/05-pdata-flag-matrix.md +52 -0
  8. data/TODO.complete/06-emf-image-rendering.md +34 -0
  9. data/TODO.complete/07-auxiliary-tables.md +47 -0
  10. data/TODO.complete/10-mdg-stereotype-registry.md +40 -0
  11. data/TODO.complete/11-gml-xsd-generation.md +42 -0
  12. data/TODO.complete/13-shapescript-interpreter.md +28 -0
  13. data/TODO.complete/20-cli-diff.md +46 -0
  14. data/TODO.complete/21-cli-render.md +26 -0
  15. data/TODO.complete/22-cli-export.md +29 -0
  16. data/TODO.complete/23-cli-mdg.md +23 -0
  17. data/TODO.complete/30-headerlines-ocp-refactor.md +72 -0
  18. data/TODO.complete/31-public-send-cleanup.md +61 -0
  19. data/TODO.complete/32-spec-coverage.md +33 -0
  20. data/TODO.complete/33-blocked-parity-gaps.md +41 -0
  21. data/TODO.complete/36-stereotype-icon-wiring.md +32 -0
  22. data/TODO.complete/37-cli-lint.md +32 -0
  23. data/TODO.complete/38-cli-query.md +26 -0
  24. data/TODO.complete/39-cli-info.md +23 -0
  25. data/TODO.complete/40-ocl-evaluator.md +31 -0
  26. data/TODO.complete/41-round-trip-spec.md +18 -0
  27. data/TODO.complete/42-html-diff-report.md +18 -0
  28. data/TODO.complete/43-json-schema-export.md +20 -0
  29. data/TODO.complete/44-remaining-qea-tables.md +40 -0
  30. data/TODO.complete/45-shapescript-extensions.md +25 -0
  31. data/TODO.complete/46-xsd-consolidation.md +20 -0
  32. data/TODO.complete/47-headerlines-legacy-removal.md +20 -0
  33. data/TODO.complete/STATUS.md +109 -0
  34. data/TODO.diagrams/01-svg-measurement-harness.md +31 -0
  35. data/TODO.diagrams/01-xmi-extension-elements-parsing.md +67 -0
  36. data/TODO.diagrams/02-diagram-element-rich-model.md +61 -0
  37. data/TODO.diagrams/02-polygon-over-rendering.md +29 -0
  38. data/TODO.diagrams/03-ea-style-svg-emitter.md +70 -0
  39. data/TODO.diagrams/03-text-over-rendering.md +30 -0
  40. data/TODO.diagrams/04-connector-routing-from-soid-eoid.md +55 -0
  41. data/TODO.diagrams/04-rect-under-rendering.md +33 -0
  42. data/TODO.diagrams/05-canvas-dimension-parity.md +51 -0
  43. data/TODO.diagrams/05-marker-rendering-per-relationship-type.md +49 -0
  44. data/TODO.diagrams/06-connector-labels.md +49 -0
  45. data/TODO.diagrams/06-group-layering-parity.md +39 -0
  46. data/TODO.diagrams/07-canvas-size-and-viewbox.md +39 -0
  47. data/TODO.diagrams/07-text-content-fidelity.md +65 -0
  48. data/TODO.diagrams/08-qea-direct-pipeline.md +46 -0
  49. data/TODO.diagrams/08-visual-regression-harness.md +34 -0
  50. data/TODO.diagrams/09-cli-render-all-diagrams.md +25 -0
  51. data/TODO.diagrams/10-stereotype-color-config.md +29 -0
  52. data/TODO.diagrams/11-elkrb-auto-layout-integration.md +53 -0
  53. data/TODO.diagrams/12-connector-labels.md +40 -0
  54. data/TODO.diagrams/12-per-entity-svg-layer-ordering.md +24 -0
  55. data/TODO.diagrams/13-marker-polygon-vs-path-dispatch.md +20 -0
  56. data/TODO.diagrams/13-per-text-type-font-sizes.md +33 -0
  57. data/TODO.diagrams/14-center-header-text.md +33 -0
  58. data/TODO.diagrams/14-filter-diagram-frame-elements.md +18 -0
  59. data/TODO.diagrams/15-deep-dead-code-audit.md +47 -0
  60. data/TODO.diagrams/15-per-element-font-from-style.md +24 -0
  61. data/TODO.diagrams/16-canvas-marker-extent.md +30 -0
  62. data/TODO.diagrams/16-document-remaining-parity-gaps.md +91 -0
  63. data/TODO.diagrams/17-italic-abstract-classes.md +28 -0
  64. data/TODO.diagrams/17-remaining-parity-gaps.md +90 -0
  65. data/TODO.diagrams/18-canvas-from-image-bounds.md +20 -0
  66. data/TODO.diagrams/18-property-lookup-index.md +42 -0
  67. data/TODO.diagrams/19-connector-sx-sy-routing.md +27 -0
  68. data/TODO.diagrams/19-replace-respond-to-with-is-a.md +31 -0
  69. data/TODO.diagrams/20-split-labels-emitter.md +56 -0
  70. data/TODO.diagrams/20-visibility-glyph-font-encoding.md +27 -0
  71. data/TODO.diagrams/21-aggregation-navigability-arrow.md +21 -0
  72. data/TODO.diagrams/21-auto-generated-legend-block.md +65 -0
  73. data/TODO.diagrams/22-ghost-element-discriminator.md +57 -0
  74. data/TODO.diagrams/22-style-based-path-grouping.md +28 -0
  75. data/TODO.diagrams/23-marker-polygon-deduplication.md +47 -0
  76. data/TODO.diagrams/23-multiplicity-label-positioning.md +29 -0
  77. data/TODO.diagrams/24-marker-anchor-structural.md +35 -0
  78. data/TODO.diagrams/24-track-marker-anchors-structurally.md +66 -0
  79. data/TODO.diagrams/24-truncate-properties-to-fit-box.md +24 -0
  80. data/TODO.diagrams/26-phantom-general-parent-rendering.md +59 -0
  81. data/TODO.diagrams/27-path-grouping-mode-detection.md +43 -0
  82. data/TODO.diagrams/28-architecture-cleanup-batch.md +47 -0
  83. data/TODO.diagrams/30-decompose-elements-groups-for.md +52 -0
  84. data/TODO.diagrams/32-examples-coverage-batch.md +78 -0
  85. data/TODO.diagrams/36-instance-specification-model.md +42 -0
  86. data/TODO.diagrams/37-element-icon-decorations.md +41 -0
  87. data/TODO.diagrams/38-html-note-and-receptions.md +32 -0
  88. data/TODO.diagrams/39-property-stereotype-discriminator.md +40 -0
  89. data/TODO.diagrams/40-standalone-1-discriminator.md +38 -0
  90. data/TODO.diagrams/41-xmi-to-svg-pipeline.md +113 -0
  91. data/TODO.diagrams/42-close-all-parity-deltas.md +69 -0
  92. data/TODO.diagrams/43-marker-deduplication-strategy.md +54 -0
  93. data/TODO.diagrams/44-element-icon-decorations.md +34 -0
  94. data/TODO.diagrams/45-phantom-parent-class-boxes.md +40 -0
  95. data/TODO.diagrams/46-user-drawn-legend-swatches.md +44 -0
  96. data/TODO.diagrams/47-attribute-line-fidelity.md +72 -0
  97. data/TODO.diagrams/48-spec-coverage-critical-emitter-components.md +45 -0
  98. data/TODO.diagrams/49-spec-coverage-orchestrators.md +25 -0
  99. data/TODO.diagrams/50-examples-parity.md +85 -0
  100. data/TODO.diagrams/51-property-oy-gap-and-remaining-gaps.md +48 -0
  101. data/TODO.diagrams/52-marker-registration-architecture.md +89 -0
  102. data/TODO.diagrams/53-remaining-example-gaps.md +86 -0
  103. data/TODO.diagrams/54-display-name-nested-classes.md +40 -0
  104. data/TODO.diagrams/55-visibility-icon-discriminator.md +58 -0
  105. data/TODO.diagrams/56-package-contents-rendering.md +148 -0
  106. data/TODO.diagrams/57-instance-specification-model.md +100 -0
  107. data/TODO.diagrams/58-domain-model-note-text.md +67 -0
  108. data/TODO.diagrams/59-classes-arrow-discriminator.md +57 -0
  109. data/TODO.diagrams/60-testschema-association-marker.md +40 -0
  110. data/TODO.diagrams/61-legend-block-rendering.md +55 -0
  111. data/TODO.diagrams/61-plateau-parity-gaps.md +59 -0
  112. data/TODO.diagrams/62-ghost-element-rendering.md +56 -0
  113. data/TODO.diagrams/63-qualified-name-wrap.md +36 -0
  114. data/TODO.diagrams/64-property-stereotype-labels.md +22 -0
  115. data/TODO.diagrams/65-path-coordinate-frame.md +62 -0
  116. data/TODO.diagrams/67-path-coordinate-frame.md +62 -0
  117. data/TODO.diagrams/68-inherited-attributes.md +65 -0
  118. data/TODO.diagrams/69-package-anchor-conditional.md +29 -0
  119. data/TODO.diagrams/70-mdg-technology-support.md +68 -0
  120. data/TODO.diagrams/71-off-canvas-parent-ghost.md +45 -0
  121. data/TODO.diagrams/72-basic-attr-divider-paths.md +39 -0
  122. data/TODO.diagrams/73-plateau-polygon-gap.md +69 -0
  123. data/TODO.diagrams/74-plateau-rect-gap.md +22 -0
  124. data/TODO.diagrams/75-plateau-text-gap.md +39 -0
  125. data/TODO.diagrams/76-basic-poly-path-gaps.md +41 -0
  126. data/TODO.diagrams/77-basic-text-overrender.md +40 -0
  127. data/TODO.diagrams/78-headerlines-ocp-refactor.md +65 -0
  128. data/TODO.diagrams/79-architecture-backlog.md +65 -0
  129. data/TODO.diagrams/80-basic-triangle-polygons.md +51 -0
  130. data/TODO.diagrams/81-plateau-path-overrender.md +53 -0
  131. data/TODO.diagrams/82-appearance-canvas-offset.md +54 -0
  132. data/TODO.diagrams/83-3d-diagram-subtitle.md +58 -0
  133. data/TODO.diagrams/84-canvas-offset-mismatches.md +61 -0
  134. data/TODO.diagrams/85-phantom-property-overrender.md +95 -0
  135. data/TODO.diagrams/86-foreign-package-rule.md +62 -0
  136. data/TODO.diagrams/87-internal-tech-encryption.md +80 -0
  137. data/TODO.diagrams/EXAMPLES_STATUS.md +88 -0
  138. data/TODO.diagrams/README.md +69 -0
  139. data/TODO.diagrams/STATUS.md +73 -0
  140. data/TODO.fixup/01-default-grouped-paths.md +34 -0
  141. data/TODO.fixup/02-marker-consolidation-per-connector.md +20 -0
  142. data/TODO.fixup/03-diagram-font-inference.md +28 -0
  143. data/TODO.fixup/04-extract-bounds-calculator.md +25 -0
  144. data/TODO.fixup/05-marker-registry-ocp.md +32 -0
  145. data/TODO.fixup/06-connector-path-bend-routing.md +28 -0
  146. data/TODO.fixup/07-edge-attachment-formula.md +22 -0
  147. data/TODO.fixup/08-visual-diff-harness.md +28 -0
  148. data/TODO.fixup/09-extract-element-compartments.md +34 -0
  149. data/TODO.fixup/10-canvas-width-overshoot-fix.md +20 -0
  150. data/TODO.fixup/11-layer-sequencer-extraction.md +18 -0
  151. data/TODO.fixup/12-path-style-harmony.md +23 -0
  152. data/TODO.fixup/13-datatype-stereotype-fallback.md +28 -0
  153. data/TODO.fixup/14-ea-exe-reverse-engineering-notes.md +56 -0
  154. data/TODO.fixup/15-enumeration-literal-rendering.md +30 -0
  155. data/TODO.fixup/16-operation-compartment-rendering.md +42 -0
  156. data/TODO.fixup/17-path-count-calibration.md +27 -0
  157. data/TODO.fixup/18-polygon-count-calibration.md +32 -0
  158. data/TODO.fixup/19-rect-count-undershoot-investigation.md +40 -0
  159. data/TODO.fixup/20-stereotype-color-from-classifier.md +18 -0
  160. data/TODO.fixup/21-visibility-filter-for-properties.md +24 -0
  161. data/TODO.fixup/22-multiplicity-display-rule-calibration.md +25 -0
  162. data/TODO.fixup/23-diagram-frame-title-element.md +31 -0
  163. data/TODO.fixup/24-note-element-rendering.md +23 -0
  164. data/TODO.fixup/25-ea-exe-disassembly-findings.md +177 -0
  165. data/TODO.fixup/26-svg-renderer-architecture.md +78 -0
  166. data/TODO.fixup/27-svg-element-emitters-disassembly.md +135 -0
  167. data/TODO.fixup/28-geometry-parser-identification.md +57 -0
  168. data/TODO.fixup/29-qea-svg-xmi-triple-comparison.md +185 -0
  169. data/TODO.fixup/30-theme-system-stub.md +46 -0
  170. data/TODO.fixup/31-theme-value-object-registry.md +17 -0
  171. data/TODO.fixup/32-styleex-on-diagram-model.md +22 -0
  172. data/TODO.fixup/33-text-encoding-decimals-rotation.md +29 -0
  173. data/TODO.fixup/34-per-type-fill-via-theme.md +18 -0
  174. data/TODO.fixup/35-stroke-width-per-theme.md +7 -0
  175. data/TODO.fixup/36-qea-rect-coord-normalization.md +24 -0
  176. data/TODO.fixup/37-package-shape-rendering.md +22 -0
  177. data/TODO.fixup/38-note-element-rendering.md +15 -0
  178. data/TODO.fixup/39-styleex-flag-honoring.md +25 -0
  179. data/TODO.fixup/40-connector-dashed-lines.md +15 -0
  180. data/TODO.fixup/41-diagram-frame-showborder-detection.md +23 -0
  181. data/TODO.fixup/42-textrenderer-centralization.md +10 -0
  182. data/TODO.fixup/43-colorresolver-unification.md +24 -0
  183. data/TODO.fixup/44-theme-aware-textrenderer.md +17 -0
  184. data/TODO.fixup/45-theme-aware-fontresolver.md +22 -0
  185. data/TODO.fixup/46-style-parameterized-by-theme.md +21 -0
  186. data/TODO.fixup/47-canvas-frame-inset.md +19 -0
  187. data/TODO.fixup/48-background-rect-format.md +15 -0
  188. data/TODO.fixup/49-all-samples-integration-test.md +17 -0
  189. data/TODO.fixup/50-svg-emitter-architecture-docs.md +33 -0
  190. data/TODO.fixup/51-promote-theme-to-ea-namespace.md +28 -0
  191. data/TODO.fixup/52-theme-definition-integrated-fills.md +26 -0
  192. data/TODO.fixup/53-theme-registry-yaml-loading.md +50 -0
  193. data/TODO.fixup/54-diagram-theme-api.md +50 -0
  194. data/TODO.fixup/55-update-eaemitter-to-ea-theme.md +23 -0
  195. data/TODO.fixup/56-theme-yaml-files.md +65 -0
  196. data/TODO.fixup/57-theme-specs-and-integration.md +27 -0
  197. data/TODO.fixup/58-ea-entry-point-theme-autoload.md +20 -0
  198. data/TODO.fixup/59-thread-theme-through-all-renderers.md +29 -0
  199. data/TODO.fixup/60-cli-theme-option.md +22 -0
  200. data/TODO.fixup/61-theme-validation.md +28 -0
  201. data/TODO.fixup/62-theme-builder-dsl.md +31 -0
  202. data/TODO.fixup/63-theme-integration-rendering-spec.md +24 -0
  203. data/TODO.fixup/64-theme-writeback-styleex.md +21 -0
  204. data/TODO.fixup/65-remove-dead-eaemitter-theme-files.md +30 -0
  205. data/TODO.fixup/66-styleex-writeback.md +22 -0
  206. data/TODO.fixup/67-delete-dead-code.md +28 -0
  207. data/TODO.fixup/68-figure-parity-status.md +82 -0
  208. data/TODO.fixup/69-package-polygon-rendering.md +54 -0
  209. data/TODO.fixup/70-tagged-values-compartment.md +39 -0
  210. data/TODO.fixup/71-auto-detect-theme-from-xmi.md +37 -0
  211. data/TODO.fixup/72-sample-exports-as-regression-specs.md +37 -0
  212. data/TODO.fixup/73-delete-dead-transformations.md +14 -0
  213. data/TODO.fixup/74-delete-dead-diagram-renderers.md +18 -0
  214. data/TODO.fixup/75-visibility-legend-icons.md +23 -0
  215. data/TODO.fixup/76-plateau-text-undershoot.md +27 -0
  216. data/TODO.fixup/77-canvas-frame-margins.md +28 -0
  217. data/TODO.fixup/78-note-element-rendering.md +30 -0
  218. data/TODO.fixup/79-operation-compartment-rendering.md +27 -0
  219. data/TODO.fixup/80-tagged-value-stereotype-visibility.md +37 -0
  220. data/TODO.fixup/81-figure-parity-current-state.md +63 -0
  221. data/config/qea_schema.yml +356 -0
  222. data/config/stereotype_colors.yml +15 -0
  223. data/config/themes/119.yml +98 -0
  224. data/config/themes/default.yml +14 -0
  225. data/examples/exports/20251010_current_plateau_v5/UML_EA.DTD +6856 -0
  226. data/examples/exports/20251010_current_plateau_v5/model.xml +96709 -0
  227. data/examples/exports/ArcGISWorkspace_template/UML_EA.DTD +6856 -0
  228. data/examples/exports/ArcGISWorkspace_template/model.xml +1427 -0
  229. data/examples/exports/UmlModel_template/UML_EA.DTD +6856 -0
  230. data/examples/exports/UmlModel_template/model.xml +23 -0
  231. data/examples/exports/basic/UML_EA.DTD +6856 -0
  232. data/examples/exports/basic/model.xml +8039 -0
  233. data/examples/exports/simple/UML_EA.DTD +6856 -0
  234. data/examples/exports/simple/model.xml +585 -0
  235. data/examples/exports/simple_example/UML_EA.DTD +6856 -0
  236. data/examples/exports/simple_example/model.xml +23 -0
  237. data/examples/exports/test/UML_EA.DTD +6856 -0
  238. data/examples/exports/test/model.xml +1638 -0
  239. data/examples/qea/basic.qea +0 -0
  240. data/frontend/README.md +51 -0
  241. data/frontend/dist/app.iife.js +24 -0
  242. data/frontend/dist/style.css +1 -0
  243. data/frontend/index.html +26 -0
  244. data/frontend/package-lock.json +2359 -0
  245. data/frontend/package.json +26 -0
  246. data/frontend/src/App.vue +59 -0
  247. data/frontend/src/app.ts +8 -0
  248. data/frontend/src/components/AppHeader.vue +127 -0
  249. data/frontend/src/components/AppSidebar.vue +119 -0
  250. data/frontend/src/components/ClassDetails.vue +247 -0
  251. data/frontend/src/components/DiagramView.vue +79 -0
  252. data/frontend/src/components/PackageDetails.vue +92 -0
  253. data/frontend/src/components/PackageTreeNode.vue +64 -0
  254. data/frontend/src/components/SearchResults.vue +48 -0
  255. data/frontend/src/components/WelcomeView.vue +87 -0
  256. data/frontend/src/shims-vue.d.ts +5 -0
  257. data/frontend/src/stores/dataStore.ts +79 -0
  258. data/frontend/src/stores/uiStore.ts +167 -0
  259. data/frontend/src/styles/main.css +1566 -0
  260. data/frontend/src/types.ts +221 -0
  261. data/frontend/tests/e2e/__screenshots__/new-dark-mode.png +0 -0
  262. data/frontend/tests/e2e/__screenshots__/new-package-details.png +0 -0
  263. data/frontend/tests/e2e/__screenshots__/new-search-modal.png +0 -0
  264. data/frontend/tests/e2e/__screenshots__/new-sidebar-collapsed.png +0 -0
  265. data/frontend/tests/e2e/__screenshots__/new-welcome.png +0 -0
  266. data/frontend/tests/e2e/render.spec.ts +188 -0
  267. data/frontend/tsconfig.json +20 -0
  268. data/frontend/vite.config.ts +26 -0
  269. data/frontend/vitest.config.ts +8 -0
  270. data/lib/ea/cli/app.rb +62 -1
  271. data/lib/ea/cli/command/diagrams.rb +37 -28
  272. data/lib/ea/cli/command/diff.rb +46 -0
  273. data/lib/ea/cli/command/export.rb +80 -0
  274. data/lib/ea/cli/command/info.rb +75 -0
  275. data/lib/ea/cli/command/lint.rb +48 -0
  276. data/lib/ea/cli/command/mdg.rb +98 -0
  277. data/lib/ea/cli/command/query.rb +34 -0
  278. data/lib/ea/cli/command/render.rb +135 -0
  279. data/lib/ea/cli/command/svg.rb +43 -6
  280. data/lib/ea/cli/command.rb +7 -0
  281. data/lib/ea/diagram/display_config.rb +184 -0
  282. data/lib/ea/diagram.rb +4 -41
  283. data/lib/ea/diff/change.rb +30 -0
  284. data/lib/ea/diff/comparator.rb +82 -0
  285. data/lib/ea/diff/html_reporter.rb +91 -0
  286. data/lib/ea/diff.rb +15 -0
  287. data/lib/ea/export/json/generator.rb +42 -0
  288. data/lib/ea/export/json.rb +12 -0
  289. data/lib/ea/export/json_schema/generator.rb +77 -0
  290. data/lib/ea/export/json_schema.rb +12 -0
  291. data/lib/ea/export/plantuml/generator.rb +73 -0
  292. data/lib/ea/export/plantuml.rb +12 -0
  293. data/lib/ea/export/xmi/generator.rb +77 -0
  294. data/lib/ea/export/xmi.rb +11 -0
  295. data/lib/ea/export/xsd/class_mapping.rb +65 -0
  296. data/lib/ea/export/xsd/generator.rb +142 -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 +14 -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 +35 -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 +53 -1
  333. data/lib/ea/model/diagram_element.rb +44 -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/query/builder.rb +108 -0
  378. data/lib/ea/query.rb +9 -0
  379. data/lib/ea/render/image_converter.rb +101 -0
  380. data/lib/ea/render.rb +10 -0
  381. data/lib/ea/shapescript/parser.rb +208 -0
  382. data/lib/ea/shapescript/renderer.rb +74 -0
  383. data/lib/ea/shapescript/shape.rb +34 -0
  384. data/lib/ea/shapescript.rb +24 -0
  385. data/lib/ea/sources/qea/adapter.rb +22 -5
  386. data/lib/ea/sources/qea/classifier_builder.rb +184 -7
  387. data/lib/ea/sources/qea/diagram_builder.rb +352 -24
  388. data/lib/ea/sources/qea/id_normalizer.rb +16 -1
  389. data/lib/ea/sources/qea/instance_builder.rb +113 -0
  390. data/lib/ea/sources/qea/legend_builder.rb +137 -0
  391. data/lib/ea/sources/qea/note_builder.rb +58 -0
  392. data/lib/ea/sources/qea/object_classifier_map.rb +6 -1
  393. data/lib/ea/sources/qea/operation_builder.rb +1 -0
  394. data/lib/ea/sources/qea/package_builder.rb +16 -0
  395. data/lib/ea/sources/qea/relationship_builder.rb +21 -5
  396. data/lib/ea/sources/qea/tagged_value_builder.rb +28 -9
  397. data/lib/ea/sources/qea/xref/legend_definition.rb +35 -0
  398. data/lib/ea/sources/qea/xref/parser.rb +150 -0
  399. data/lib/ea/sources/qea/xref/property_definition.rb +29 -0
  400. data/lib/ea/sources/qea/xref/record.rb +41 -0
  401. data/lib/ea/sources/qea/xref/stereotype_application.rb +37 -0
  402. data/lib/ea/sources/qea/xref.rb +27 -0
  403. data/lib/ea/sources/qea.rb +4 -0
  404. data/lib/ea/sources/xmi/adapter.rb +80 -2
  405. data/lib/ea/sources/xmi/annotation_builder.rb +1 -1
  406. data/lib/ea/sources/xmi/classifier_builder.rb +10 -1
  407. data/lib/ea/sources/xmi/diagram_builder.rb +242 -45
  408. data/lib/ea/sources/xmi/extension_elements.rb +74 -0
  409. data/lib/ea/sources/xmi/extension_geometry_parser.rb +124 -0
  410. data/lib/ea/sources/xmi/extension_style_parser.rb +91 -0
  411. data/lib/ea/sources/xmi/note_builder.rb +46 -0
  412. data/lib/ea/sources/xmi/property_builder.rb +9 -0
  413. data/lib/ea/sources/xmi/stereotype_builder.rb +54 -0
  414. data/lib/ea/sources/xmi/tag_builder.rb +45 -0
  415. data/lib/ea/sources/xmi/umldi_keyword_extractor.rb +87 -0
  416. data/lib/ea/sources/xmi.rb +7 -0
  417. data/lib/ea/spa/output/sharded_multi_file_strategy.rb +18 -3
  418. data/lib/ea/spa/output/single_file_strategy.rb +8 -2
  419. data/lib/ea/spa/output/strategy.rb +34 -0
  420. data/lib/ea/svg/connector_router.rb +134 -0
  421. data/lib/ea/svg/ea_emitter/background.rb +15 -0
  422. data/lib/ea/svg/ea_emitter/bounds_calculator.rb +142 -0
  423. data/lib/ea/svg/ea_emitter/canvas.rb +88 -0
  424. data/lib/ea/svg/ea_emitter/color_resolver.rb +56 -0
  425. data/lib/ea/svg/ea_emitter/compartment/attributes.rb +28 -0
  426. data/lib/ea/svg/ea_emitter/compartment/constraints.rb +30 -0
  427. data/lib/ea/svg/ea_emitter/compartment/enum_literals.rb +86 -0
  428. data/lib/ea/svg/ea_emitter/compartment/header.rb +32 -0
  429. data/lib/ea/svg/ea_emitter/compartment/header_divider.rb +57 -0
  430. data/lib/ea/svg/ea_emitter/compartment/instance_slots.rb +96 -0
  431. data/lib/ea/svg/ea_emitter/compartment/instance_underline.rb +67 -0
  432. data/lib/ea/svg/ea_emitter/compartment/note_body.rb +52 -0
  433. data/lib/ea/svg/ea_emitter/compartment/note_text.rb +102 -0
  434. data/lib/ea/svg/ea_emitter/compartment/operations.rb +38 -0
  435. data/lib/ea/svg/ea_emitter/compartment/package_contents.rb +72 -0
  436. data/lib/ea/svg/ea_emitter/compartment/package_from_parent.rb +136 -0
  437. data/lib/ea/svg/ea_emitter/compartment/shape.rb +82 -0
  438. data/lib/ea/svg/ea_emitter/compartment/tagged_values.rb +30 -0
  439. data/lib/ea/svg/ea_emitter/compartment.rb +58 -0
  440. data/lib/ea/svg/ea_emitter/connectors.rb +84 -0
  441. data/lib/ea/svg/ea_emitter/diagram_frame.rb +143 -0
  442. data/lib/ea/svg/ea_emitter/document.rb +35 -0
  443. data/lib/ea/svg/ea_emitter/element/attribute_line_builder.rb +117 -0
  444. data/lib/ea/svg/ea_emitter/element/attribute_renderer.rb +119 -0
  445. data/lib/ea/svg/ea_emitter/element/bcol_decoder.rb +29 -0
  446. data/lib/ea/svg/ea_emitter/element/compartment_geometry.rb +113 -0
  447. data/lib/ea/svg/ea_emitter/element/constraint_renderer.rb +53 -0
  448. data/lib/ea/svg/ea_emitter/element/divider_renderer.rb +17 -0
  449. data/lib/ea/svg/ea_emitter/element/enumeration_literal_renderer.rb +34 -0
  450. data/lib/ea/svg/ea_emitter/element/filter.rb +44 -0
  451. data/lib/ea/svg/ea_emitter/element/hand_draw_shape_renderer.rb +77 -0
  452. data/lib/ea/svg/ea_emitter/element/header_line_pipeline.rb +43 -0
  453. data/lib/ea/svg/ea_emitter/element/header_line_provider/instance_spec.rb +27 -0
  454. data/lib/ea/svg/ea_emitter/element/header_line_provider/name.rb +76 -0
  455. data/lib/ea/svg/ea_emitter/element/header_line_provider/parent_ghost.rb +25 -0
  456. data/lib/ea/svg/ea_emitter/element/header_line_provider/stereotype_label.rb +68 -0
  457. data/lib/ea/svg/ea_emitter/element/header_line_provider.rb +19 -0
  458. data/lib/ea/svg/ea_emitter/element/header_lines.rb +27 -0
  459. data/lib/ea/svg/ea_emitter/element/header_renderer.rb +48 -0
  460. data/lib/ea/svg/ea_emitter/element/icon_renderer.rb +109 -0
  461. data/lib/ea/svg/ea_emitter/element/legend_renderer.rb +177 -0
  462. data/lib/ea/svg/ea_emitter/element/note_shape_renderer.rb +112 -0
  463. data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +100 -0
  464. data/lib/ea/svg/ea_emitter/element/package_shape_renderer.rb +181 -0
  465. data/lib/ea/svg/ea_emitter/element/row_icon_renderer.rb +156 -0
  466. data/lib/ea/svg/ea_emitter/element/shape_renderer.rb +17 -0
  467. data/lib/ea/svg/ea_emitter/element/stereotype_icon_renderer.rb +82 -0
  468. data/lib/ea/svg/ea_emitter/element/tagged_value_renderer.rb +50 -0
  469. data/lib/ea/svg/ea_emitter/element/text_escape.rb +20 -0
  470. data/lib/ea/svg/ea_emitter/element.rb +37 -0
  471. data/lib/ea/svg/ea_emitter/elements.rb +357 -0
  472. data/lib/ea/svg/ea_emitter/font_resolver.rb +111 -0
  473. data/lib/ea/svg/ea_emitter/ghost_labels.rb +66 -0
  474. data/lib/ea/svg/ea_emitter/label/end_label.rb +266 -0
  475. data/lib/ea/svg/ea_emitter/label/midpoint_label.rb +101 -0
  476. data/lib/ea/svg/ea_emitter/label/registry.rb +50 -0
  477. data/lib/ea/svg/ea_emitter/label.rb +23 -0
  478. data/lib/ea/svg/ea_emitter/labels.rb +130 -0
  479. data/lib/ea/svg/ea_emitter/layer.rb +20 -0
  480. data/lib/ea/svg/ea_emitter/layer_sequencer.rb +93 -0
  481. data/lib/ea/svg/ea_emitter/marker/arrow_path.rb +52 -0
  482. data/lib/ea/svg/ea_emitter/marker/diamond.rb +57 -0
  483. data/lib/ea/svg/ea_emitter/marker/kind.rb +35 -0
  484. data/lib/ea/svg/ea_emitter/marker/open_triangle.rb +27 -0
  485. data/lib/ea/svg/ea_emitter/marker/package_import.rb +39 -0
  486. data/lib/ea/svg/ea_emitter/marker/plus.rb +40 -0
  487. data/lib/ea/svg/ea_emitter/marker/registry.rb +42 -0
  488. data/lib/ea/svg/ea_emitter/marker.rb +37 -0
  489. data/lib/ea/svg/ea_emitter/markers.rb +404 -0
  490. data/lib/ea/svg/ea_emitter/render_context.rb +52 -0
  491. data/lib/ea/svg/ea_emitter/style.rb +15 -0
  492. data/lib/ea/svg/ea_emitter/text_renderer.rb +92 -0
  493. data/lib/ea/svg/ea_emitter.rb +51 -0
  494. data/lib/ea/svg/parity/checker.rb +105 -0
  495. data/lib/ea/svg/parity/source.rb +59 -0
  496. data/lib/ea/svg/parity/suite.rb +113 -0
  497. data/lib/ea/svg/parity.rb +14 -0
  498. data/lib/ea/svg/stereotype_color_resolver.rb +50 -0
  499. data/lib/ea/svg.rb +4 -15
  500. data/lib/ea/theme/definition.rb +176 -0
  501. data/lib/ea/theme/loader.rb +125 -0
  502. data/lib/ea/theme/registry.rb +96 -0
  503. data/lib/ea/theme.rb +37 -0
  504. data/lib/ea/transformations.rb +5 -88
  505. data/lib/ea/version.rb +4 -1
  506. data/lib/ea.rb +14 -4
  507. data/lib/tasks/svg_bench.rake +87 -0
  508. data/lutaml-uml-0.5.1.gem +0 -0
  509. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes-list.png +0 -0
  510. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes.png +0 -0
  511. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-2-gradients.png +0 -0
  512. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-3-colors.png +0 -0
  513. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-4-appearance-fonts.png +0 -0
  514. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-4-appearance.png +0 -0
  515. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-5-behavior.png +0 -0
  516. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-6-sequence.png +0 -0
  517. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram.png +0 -0
  518. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-2-objects.png +0 -0
  519. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-3-links.png +0 -0
  520. metadata +473 -26
  521. data/lib/ea/diagram/configuration.rb +0 -379
  522. data/lib/ea/diagram/element_renderers/base_renderer.rb +0 -77
  523. data/lib/ea/diagram/element_renderers/class_renderer.rb +0 -323
  524. data/lib/ea/diagram/element_renderers/connector_renderer.rb +0 -41
  525. data/lib/ea/diagram/element_renderers/package_renderer.rb +0 -61
  526. data/lib/ea/diagram/element_renderers.rb +0 -43
  527. data/lib/ea/diagram/extractor.rb +0 -566
  528. data/lib/ea/diagram/layout_engine.rb +0 -170
  529. data/lib/ea/diagram/path_builder.rb +0 -202
  530. data/lib/ea/diagram/style_parser.rb +0 -42
  531. data/lib/ea/diagram/style_resolver.rb +0 -291
  532. data/lib/ea/diagram/svg_renderer.rb +0 -274
  533. data/lib/ea/diagram/util.rb +0 -73
  534. data/lib/ea/svg/bounds_calculator.rb +0 -74
  535. data/lib/ea/svg/connector_path.rb +0 -138
  536. data/lib/ea/svg/element_box.rb +0 -256
  537. data/lib/ea/svg/renderer.rb +0 -77
  538. data/lib/ea/svg/style_resolver.rb +0 -72
  539. data/lib/ea/transformations/configuration.rb +0 -333
  540. data/lib/ea/transformations/format_registry.rb +0 -366
  541. data/lib/ea/transformations/parsers/base_parser.rb +0 -482
  542. data/lib/ea/transformations/parsers/qea_parser.rb +0 -401
  543. data/lib/ea/transformations/parsers/xmi_parser.rb +0 -243
  544. data/lib/ea/transformations/transformation_engine.rb +0 -390
@@ -3,78 +3,275 @@
3
3
  module Ea
4
4
  module Sources
5
5
  module Xmi
6
- # Translates XMI uml:Diagram elements into Ea::Model::Diagram
7
- # instances. Coordinates come from the UML-DI owned_element
8
- # bounds; element references come from the model_element
9
- # attribute.
6
+ # Translates EA diagrams from the XMI source into Ea::Model
7
+ # instances. Uses the rich placement data inside the
8
+ # <xmi:Extension> block (Left/Top/Right/Bottom, BCol, font,
9
+ # SOID/EOID, EDGE, label boxes) when present. Falls back to
10
+ # the UML-DI owned_element bounds otherwise.
10
11
  class DiagramBuilder
11
- attr_reader :root
12
+ attr_reader :root, :xmi_path
12
13
 
13
- def initialize(root)
14
+ def initialize(root, xmi_path = nil)
14
15
  @root = root
16
+ @xmi_path = xmi_path
15
17
  end
16
18
 
17
19
  def build_all
18
- diagrams = Array(root.model.diagram)
19
- diagrams.map { |d| build_one(d) }
20
+ ext_diagrams = extension_diagrams
21
+ return [] if ext_diagrams.empty?
22
+
23
+ ext_diagrams.map { |d| build_one(d) }
24
+ end
25
+
26
+ # The XMI stores diagrams in two places:
27
+ # - root.model.diagram — UML-DI shape (often just one or none)
28
+ # - root.extension.diagrams.diagram — the rich EA extension
29
+ # block with placement/style/connector data (the source of
30
+ # truth for rendering).
31
+ # We use the extension block when present; fall back to UML-DI
32
+ # otherwise.
33
+ def extension_diagrams
34
+ ext = root.extension
35
+ return [] unless ext&.diagrams
36
+
37
+ ext.diagrams.diagram.to_a
20
38
  end
21
39
 
22
- def build_one(diagram)
23
- id = IdNormalizer.from_xmi_id(diagram.id)
24
- ext_diagram = extension_diagram_for(id)
40
+ def build_one(ext_diagram)
41
+ id = IdNormalizer.from_xmi_id(ext_diagram.id)
42
+ ext_elements = ExtensionElements.new(ext_diagram)
43
+ placed_elements = ext_elements.placed_elements
44
+ placed_connectors = ext_elements.placed_connectors
45
+ props = ext_diagram.properties
46
+ umldi_keywords = keywords_for(id)
47
+
25
48
  Ea::Model::Diagram.new(
26
49
  id: id,
27
- name: ext_diagram&.properties&.name,
28
- package_id: ext_diagram&.model&.package,
29
- diagram_type: ext_diagram&.properties&.type&.split(":")&.last&.downcase,
30
- bounds: bounds_for(diagram),
31
- elements: build_elements(diagram, id),
32
- connectors: [],
33
- annotations: AnnotationBuilder.from_element(diagram, id)
50
+ name: props&.name || ext_diagram.id,
51
+ package_id: ext_diagram.model&.package,
52
+ diagram_type: props&.type&.split(":")&.last&.downcase,
53
+ bounds: canvas_bounds(placed_elements),
54
+ style: style_for(ext_diagram),
55
+ style_ex: style_ex_for(ext_diagram),
56
+ elements: build_elements(placed_elements, id, umldi_keywords),
57
+ connectors: build_connectors(placed_connectors,
58
+ placed_elements, id),
59
+ annotations: []
34
60
  )
35
61
  end
36
62
 
63
+ # EA's XMI stores Style (a.k.a. Style1) in `style1.value` —
64
+ # the per-diagram feature visibility flags (HideAtts, HideOps,
65
+ # HideStereo, etc.). Parsed by DisplayConfig alongside
66
+ # StyleEx.
67
+ def style_for(ext_diagram)
68
+ style1 = ext_diagram.style1
69
+ return nil unless style1
70
+
71
+ style1.value
72
+ end
73
+
74
+ # EA's XMI stores StyleEx in the diagram's `style2.value`
75
+ # attribute (alongside Style1 in `style1.value`). The value
76
+ # is a semicolon-separated key=value string carrying the
77
+ # Theme=:NNN identifier and behavioral flags (SuppressFOC,
78
+ # AttPkg, ShowNotes, etc.) — the same shape our Diagram
79
+ # model expects in `style_ex`.
80
+ def style_ex_for(ext_diagram)
81
+ style2 = ext_diagram.style2
82
+ return nil unless style2
83
+
84
+ style2.value
85
+ end
86
+
37
87
  private
38
88
 
39
- def extension_diagram_for(diagram_id)
40
- return nil unless root.extension&.diagrams
89
+ def canvas_bounds(placed_elements)
90
+ return nil if placed_elements.empty?
41
91
 
42
- root.extension.diagrams.diagram.find { |d| d.id == diagram_id }
92
+ left = placed_elements.map { |e| e.geometry.img_left || e.geometry.left }.compact.min
93
+ top = placed_elements.map { |e| e.geometry.img_top || e.geometry.top }.compact.min
94
+ right = placed_elements.map { |e| e.geometry.img_right || e.geometry.right }.compact.max
95
+ bottom = placed_elements.map { |e| e.geometry.img_bottom || e.geometry.bottom }.compact.max
96
+ return nil unless left && top && right && bottom
97
+
98
+ Ea::Model::Bounds.new(x: 0, y: 0,
99
+ width: right - left,
100
+ height: bottom - top)
43
101
  end
44
102
 
45
- def bounds_for(diagram)
46
- first_bounds = diagram.owned_element.flat_map(&:bounds).first
47
- return nil unless first_bounds
103
+ def build_elements(placed, owner_id, umldi_keywords = {})
104
+ placed.map { |p| build_element(p, owner_id, umldi_keywords) }
105
+ end
48
106
 
49
- Ea::Model::Bounds.new(
50
- x: first_bounds.x || 0,
51
- y: first_bounds.y || 0,
52
- width: first_bounds.width || 0,
53
- height: first_bounds.height || 0
107
+ def build_element(placed, owner_id, umldi_keywords = {})
108
+ geom = placed.geometry
109
+ style = placed.style
110
+ Ea::Model::DiagramElement.new(
111
+ id: IdNormalizer.synthetic_id(owner_id, "elem", placed.subject),
112
+ diagram_id: owner_id,
113
+ model_element_ref: placed.subject,
114
+ bounds: build_bounds(geom.left, geom.top, geom.right, geom.bottom),
115
+ image_bounds: build_bounds(geom.img_left, geom.img_top, geom.img_right, geom.img_bottom),
116
+ background_color: style.background_color,
117
+ line_color: style.line_color,
118
+ line_width: style.line_width,
119
+ font_family: style.font_family,
120
+ font_size: style.font_size,
121
+ font_bold: style.bold,
122
+ font_italic: style.italic,
123
+ font_underline: style.underline,
124
+ show_tagged_values: style.show_tagged_values,
125
+ icon_visibility: style.hide_icon&.to_s,
126
+ z_order: placed.seqno,
127
+ duid: style.duid,
128
+ umldi_keyword: umldi_keywords[placed.subject],
129
+ style: {}
54
130
  )
55
131
  end
56
132
 
57
- def build_elements(diagram, owner_diagram_id)
58
- diagram.owned_element.flat_map do |oe|
59
- oe.bounds.each_with_index.map do |bnd, idx|
60
- build_element(oe, bnd, owner_diagram_id, idx)
133
+ def build_bounds(left, top, right, bottom)
134
+ return nil unless left && top && right && bottom
135
+
136
+ Ea::Model::Bounds.new(x: left, y: top,
137
+ width: right - left,
138
+ height: bottom - top)
139
+ end
140
+
141
+ def build_connectors(placed_connectors, placed_elements, owner_id)
142
+ # Index elements by DUID so we can resolve SOID/EOID.
143
+ by_duid = placed_elements.each_with_object({}) do |pe, acc|
144
+ acc[pe.style.duid] = pe if pe.style.duid
145
+ end
146
+
147
+ placed_connectors.map { |p| build_connector(p, by_duid, owner_id) }
148
+ end
149
+
150
+ def build_connector(placed, by_duid, owner_id)
151
+ geom = placed.geometry
152
+ style = placed.style
153
+ source = by_duid[style.soid]
154
+ target = by_duid[style.eoid]
155
+ waypoints = compute_waypoints(geom, source, target)
156
+ ext_meta = connector_meta(placed.subject)
157
+ Ea::Model::DiagramConnector.new(
158
+ id: IdNormalizer.synthetic_id(owner_id, "conn", placed.subject),
159
+ diagram_id: owner_id,
160
+ relationship_ref: placed.subject,
161
+ source_duid: style.soid,
162
+ target_duid: style.eoid,
163
+ source_element_ref: source && synthetic_element_id(owner_id, source),
164
+ target_element_ref: target && synthetic_element_id(owner_id, target),
165
+ source_edge: geom.edge,
166
+ target_edge: geom.edge,
167
+ connector_type: ext_meta[:ea_type],
168
+ direction: ext_meta[:direction],
169
+ waypoints: waypoints,
170
+ style: {},
171
+ line_color: style.color,
172
+ line_width: style.line_width,
173
+ hidden: style.hidden,
174
+ label_boxes: serialize_label_boxes(geom.label_boxes)
175
+ )
176
+ end
177
+
178
+ def connector_meta(subject_id)
179
+ connector = connector_index[subject_id]
180
+ return { ea_type: nil, direction: nil } unless connector
181
+
182
+ props = connector.properties
183
+ {
184
+ ea_type: props&.ea_type,
185
+ direction: props&.direction
186
+ }
187
+ end
188
+
189
+ def connector_index
190
+ @connector_index ||= begin
191
+ conns = root.extension&.connectors&.connector || []
192
+ conns.each_with_object({}) do |c, acc|
193
+ acc[c.idref] = c if c.idref
61
194
  end
62
195
  end
63
196
  end
64
197
 
65
- def build_element(owned_element, bounds, diagram_id, idx)
66
- Ea::Model::DiagramElement.new(
67
- id: IdNormalizer.synthetic_id(diagram_id, "elem", idx.to_s),
68
- diagram_id: diagram_id,
69
- model_element_ref: IdNormalizer.from_xmi_id(owned_element.model_element),
70
- bounds: Ea::Model::Bounds.new(
71
- x: bounds.x || 0,
72
- y: bounds.y || 0,
73
- width: bounds.width || 0,
74
- height: bounds.height || 0
75
- ),
76
- style: {}
198
+ def umldi_extractor
199
+ return nil unless xmi_path
200
+
201
+ @umldi_extractor ||= UmldiKeywordExtractor.new(xmi_path)
202
+ end
203
+
204
+ def keywords_for(diagram_id)
205
+ return {} unless umldi_extractor
206
+
207
+ umldi_extractor.keywords_for_diagram(diagram_id)
208
+ end
209
+
210
+ def synthetic_element_id(owner_id, placed)
211
+ IdNormalizer.synthetic_id(owner_id, "elem", placed.subject)
212
+ end
213
+
214
+ def compute_waypoints(geom, source, target)
215
+ src_bounds = bounds_from_placed(source)
216
+ tgt_bounds = bounds_from_placed(target)
217
+ return [] unless src_bounds && tgt_bounds
218
+
219
+ router = Ea::Svg::ConnectorRouter.new(
220
+ source_bounds: src_bounds,
221
+ target_bounds: tgt_bounds,
222
+ edge_code: geom.edge,
223
+ source_delta: [geom.sx, geom.sy],
224
+ target_delta: [geom.ex, geom.ey],
225
+ bend_path: geom.bend_points || []
77
226
  )
227
+ router.waypoints.map do |x, y|
228
+ Ea::Model::Waypoint.new(position: Ea::Model::Point.new(x: x, y: y))
229
+ end
230
+ end
231
+
232
+ def bounds_from_placed(placed)
233
+ return nil unless placed
234
+
235
+ g = placed.geometry
236
+ return nil unless g.left && g.top && g.right && g.bottom
237
+
238
+ Ea::Model::Bounds.new(x: g.left, y: g.top,
239
+ width: g.right - g.left,
240
+ height: g.bottom - g.top)
241
+ end
242
+
243
+ def edge_point(placed, edge_code, _end_kind)
244
+ return nil unless placed
245
+
246
+ geom = placed.geometry
247
+ left = geom.left
248
+ top = geom.top
249
+ right = geom.right
250
+ bottom = geom.bottom
251
+ return nil unless left && top && right && bottom
252
+
253
+ case edge_code
254
+ when 1 then [(left + right) / 2, top] # top
255
+ when 2 then [right, (top + bottom) / 2] # right
256
+ when 3 then [(left + right) / 2, bottom] # bottom
257
+ when 4 then [left, (top + bottom) / 2] # left
258
+ else [(left + right) / 2, (top + bottom) / 2]
259
+ end
260
+ end
261
+
262
+ def serialize_label_boxes(label_boxes)
263
+ return {} unless label_boxes
264
+
265
+ label_boxes.transform_values do |box|
266
+ {
267
+ "cx" => box.cx, "cy" => box.cy,
268
+ "ox" => box.ox, "oy" => box.oy,
269
+ "bold" => box.bold,
270
+ "italic" => box.italic,
271
+ "underline" => box.underline,
272
+ "align" => box.align
273
+ }
274
+ end
78
275
  end
79
276
  end
80
277
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Sources
5
+ module Xmi
6
+ # Walks the <xmi:Extension>/<elements> block for a single
7
+ # EA diagram. Returns two arrays: placed elements and placed
8
+ # connectors, each carrying the parsed geometry + style data.
9
+ #
10
+ # The xmi gem exposes the raw rows via Sparx::Diagram::Elements.
11
+ # This class adds EA-aware parsing on top.
12
+ class ExtensionElements
13
+ attr_reader :diagram
14
+
15
+ def initialize(diagram)
16
+ @diagram = diagram
17
+ end
18
+
19
+ def placed_elements
20
+ rows.filter_map { |row| build_placed_element(row) }
21
+ end
22
+
23
+ def placed_connectors
24
+ rows.filter_map { |row| build_placed_connector(row) }
25
+ end
26
+
27
+ private
28
+
29
+ def rows
30
+ elements_collection = diagram&.elements
31
+ return [] unless elements_collection
32
+
33
+ elements_collection.element.to_a
34
+ end
35
+
36
+ def build_placed_element(row)
37
+ return nil unless row.subject
38
+
39
+ placement = ExtensionGeometryParser.parse(row.geometry)
40
+ return nil unless placement.left || placement.img_left
41
+
42
+ style = ExtensionStyleParser.parse(row.style)
43
+ PlacedElement.new(
44
+ subject: row.subject,
45
+ seqno: row.seqno,
46
+ geometry: placement,
47
+ style: style
48
+ )
49
+ end
50
+
51
+ def build_placed_connector(row)
52
+ return nil unless row.subject
53
+
54
+ placement = ExtensionGeometryParser.parse(row.geometry)
55
+ style = ExtensionStyleParser.parse(row.style)
56
+ # Connectors carry SOID/EOID in their style; geometry has
57
+ # only bend routing.
58
+ return nil unless style.soid && style.eoid
59
+
60
+ PlacedConnector.new(
61
+ subject: row.subject,
62
+ geometry: placement,
63
+ style: style
64
+ )
65
+ end
66
+
67
+ # Value types — the diagram builder translates these into
68
+ # Ea::Model::DiagramElement / DiagramConnector.
69
+ PlacedElement = Struct.new(:subject, :seqno, :geometry, :style, keyword_init: true)
70
+ PlacedConnector = Struct.new(:subject, :geometry, :style, keyword_init: true)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Sources
5
+ module Xmi
6
+ # Parses EA's packed geometry string from
7
+ # <xmi:Extension>/<elements>/<element>/@geometry. Returns a
8
+ # typed Placement struct with separate fields for placement
9
+ # and bend routing. Label boxes decoded into a Hash keyed by
10
+ # EA's role names (:llb, :llt, :lrt, :lrb).
11
+ module ExtensionGeometryParser
12
+ LABEL_BOX_KEYS = %i[llb llt lrt lrb lmt lmb irhs ilhs].freeze
13
+
14
+ module_function
15
+
16
+ def parse(geometry_string)
17
+ return Placement.new if geometry_string.nil? || geometry_string.empty?
18
+
19
+ kv = split_pairs(geometry_string)
20
+ Placement.new(
21
+ left: int(kv["Left"]),
22
+ top: int(kv["Top"]),
23
+ right: int(kv["Right"]),
24
+ bottom: int(kv["Bottom"]),
25
+ img_left: int(kv["imgL"]),
26
+ img_top: int(kv["imgT"]),
27
+ img_right: int(kv["imgR"]),
28
+ img_bottom: int(kv["imgB"]),
29
+ sx: int(kv["SX"]),
30
+ sy: int(kv["SY"]),
31
+ ex: int(kv["EX"]),
32
+ ey: int(kv["EY"]),
33
+ edge: int(kv["EDGE"]),
34
+ label_boxes: extract_label_boxes(geometry_string),
35
+ path: kv["Path"],
36
+ bend_points: parse_bend_path(kv["Path"])
37
+ )
38
+ end
39
+
40
+ # EA's Path= field uses the format `x1:y1$x2:y2$...` where
41
+ # each pair is a bend point. Coordinates are absolute pixel
42
+ # positions on the diagram canvas.
43
+ def parse_bend_path(raw)
44
+ return [] if raw.nil? || raw.empty?
45
+
46
+ raw.split("$").filter_map do |pair|
47
+ next nil unless pair.include?(":")
48
+
49
+ x_str, y_str = pair.split(":", 2)
50
+ x = int(x_str)
51
+ y = int(y_str)
52
+ [x, y] if x && y
53
+ end
54
+ end
55
+
56
+ def split_pairs(str)
57
+ str.split(";").filter_map do |pair|
58
+ next nil unless pair.include?("=")
59
+ next nil if pair.start_with?("$") # $LLB= etc handled separately
60
+
61
+ key, value = pair.split("=", 2)
62
+ [key, value] if key && value
63
+ end.to_h
64
+ end
65
+
66
+ def int(value)
67
+ return nil if value.nil? || value.empty?
68
+
69
+ Integer(value)
70
+ rescue ArgumentError
71
+ nil
72
+ end
73
+
74
+ def extract_label_boxes(geometry_string)
75
+ geometry_string.to_s.scan(/\$?([A-Z]{3})=((?:[A-Za-z]{1,4}=-?\d+:)+)/).each_with_object({}) do |(key, body), acc|
76
+ sym = key.downcase.to_sym
77
+ next unless LABEL_BOX_KEYS.include?(sym)
78
+ next if body.nil? || body.empty?
79
+
80
+ acc[sym] = parse_label_body(body)
81
+ end
82
+ end
83
+
84
+ def parse_label_body(body)
85
+ pairs = body.to_s.split(":").filter_map do |pair|
86
+ next nil unless pair.include?("=")
87
+
88
+ k, v = pair.split("=", 2)
89
+ [k, int(v)] if k && v
90
+ end
91
+ hash = pairs.to_h
92
+ LabelBox.new(
93
+ cx: hash["CX"],
94
+ cy: hash["CY"],
95
+ ox: hash["OX"],
96
+ oy: hash["OY"],
97
+ bold: hash["BLD"] == 1,
98
+ italic: hash["ITA"] == 1,
99
+ underline: hash["UND"] == 1,
100
+ hidden: hash["HDN"] == 1,
101
+ align: hash["ALN"],
102
+ direction: hash["DIR"],
103
+ rotation: hash["ROT"]
104
+ )
105
+ end
106
+
107
+ Placement = Struct.new(
108
+ :left, :top, :right, :bottom,
109
+ :img_left, :img_top, :img_right, :img_bottom,
110
+ :sx, :sy, :ex, :ey, :edge,
111
+ :label_boxes, :path, :bend_points,
112
+ keyword_init: true
113
+ )
114
+
115
+ LabelBox = Struct.new(
116
+ :cx, :cy, :ox, :oy,
117
+ :bold, :italic, :underline, :hidden,
118
+ :align, :direction, :rotation,
119
+ keyword_init: true
120
+ )
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Sources
5
+ module Xmi
6
+ # Parses EA's packed style string from
7
+ # <xmi:Extension>/<elements>/<element>/@style. Returns a typed
8
+ # Style struct so renderers consume typed fields.
9
+ module ExtensionStyleParser
10
+ module_function
11
+
12
+ def parse(style_string)
13
+ return Style.new if style_string.nil? || style_string.empty?
14
+
15
+ kv = split_pairs(style_string)
16
+ Style.new(
17
+ background_color: int(kv["BCol"]),
18
+ line_color: int(kv["LCol"]),
19
+ line_width: int(kv["LWth"] || kv["LWidth"]),
20
+ font_family: kv["font"],
21
+ font_size: scale_font_size(kv["fontsz"]),
22
+ bold: truthy?(kv["bold"]),
23
+ italic: truthy?(kv["italic"]),
24
+ underline: truthy?(kv["ul"]),
25
+ black: truthy?(kv["black"]),
26
+ hide_icon: tri_state_icon(kv["HideIcon"]),
27
+ show_tagged_values: truthy?(kv["Tag"]),
28
+ duid: kv["DUID"],
29
+ soid: kv["SOID"],
30
+ eoid: kv["EOID"],
31
+ mode: int(kv["Mode"]),
32
+ color: int(kv["Color"]),
33
+ hidden: truthy?(kv["Hidden"]),
34
+ raw: style_string
35
+ )
36
+ end
37
+
38
+ # HideIcon tri-state: "1" → :hidden, "0" → :shown,
39
+ # missing → nil (use the diagram default, which EA treats
40
+ # as hidden in practice).
41
+ def tri_state_icon(value)
42
+ return nil if value.nil? || value.empty?
43
+ return :hidden if value == "1" || value == "true" || value == "-1"
44
+ return :shown if value == "0"
45
+
46
+ nil
47
+ end
48
+
49
+ def split_pairs(str)
50
+ str.split(";").filter_map do |pair|
51
+ next nil unless pair.include?("=")
52
+
53
+ key, value = pair.split("=", 2)
54
+ [key, value] if key && value && !value.empty?
55
+ end.to_h
56
+ end
57
+
58
+ def int(value)
59
+ return nil if value.nil? || value.empty?
60
+
61
+ Integer(value)
62
+ rescue ArgumentError
63
+ nil
64
+ end
65
+
66
+ def truthy?(value)
67
+ value == "1" || value == "true" || value == "-1"
68
+ end
69
+
70
+ def scale_font_size(value)
71
+ return 13 if value.nil? || value.empty?
72
+
73
+ pct = int(value)
74
+ return 13 if pct.nil? || pct.zero?
75
+
76
+ (pct * 13 / 100).round
77
+ end
78
+
79
+ Style = Struct.new(
80
+ :background_color, :line_color, :line_width,
81
+ :font_family, :font_size,
82
+ :bold, :italic, :underline, :black, :hide_icon,
83
+ :show_tagged_values,
84
+ :duid, :soid, :eoid, :mode, :color, :hidden,
85
+ :raw,
86
+ keyword_init: true
87
+ )
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ module Ea
6
+ module Sources
7
+ module Xmi
8
+ # Extracts Note elements from EA's XMI extension block.
9
+ # Notes are documentation entries (`<element xmi:type="uml:Note">`)
10
+ # inside `<xmi:Extension>/<elements>`. Each carries a
11
+ # `<properties documentation="..."/>` child with the body text.
12
+ #
13
+ # The xmi gem parses Note elements as part of the extension
14
+ # block; we walk them and project to Ea::Model::Note.
15
+ class NoteBuilder
16
+ attr_reader :root
17
+
18
+ def initialize(root)
19
+ @root = root
20
+ end
21
+
22
+ def build_all
23
+ elements = root.extension&.elements
24
+ return [] unless elements
25
+
26
+ elements.element.to_a.filter_map { |e| build_one(e) }
27
+ end
28
+
29
+ private
30
+
31
+ def build_one(ext_element)
32
+ type = ext_element.type
33
+ return nil unless type == "uml:Note"
34
+
35
+ props = ext_element.properties
36
+ Ea::Model::Note.new(
37
+ id: ext_element.idref,
38
+ name: ext_element.name,
39
+ body: props&.documentation,
40
+ note_type: "Note"
41
+ )
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -19,6 +19,7 @@ module Ea
19
19
  id: id,
20
20
  name: attr.name,
21
21
  owner_id: owner.id,
22
+ type_ref: type_ref_for(attr),
22
23
  type_name: type_name_for(attr),
23
24
  qualified_name: "#{owner.name}::#{attr.name}",
24
25
  multiplicity_lower: parse_lower(attr),
@@ -29,6 +30,7 @@ module Ea
29
30
  is_ordered: boolean(attr.is_ordered),
30
31
  is_unique: boolean(attr.is_unique),
31
32
  aggregation: attr.aggregation || "none",
33
+ association_id: IdNormalizer.from_xmi_id(attr.association),
32
34
  visibility: attr.visibility,
33
35
  annotations: AnnotationBuilder.from_element(attr, id)
34
36
  )
@@ -44,6 +46,13 @@ module Ea
44
46
  attr.uml_type&.idref
45
47
  end
46
48
 
49
+ def type_ref_for(attr)
50
+ # XMI <type xmi:idref=...> is the type classifier's GUID.
51
+ # The adapter's resolve_type_names walks this against the
52
+ # model to attach the in-model qualified name.
53
+ attr.uml_type&.idref
54
+ end
55
+
47
56
  def parse_lower(attr)
48
57
  value = attr.lower_value
49
58
  return 1 unless value