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
@@ -26,6 +26,12 @@ module Ea
26
26
  package_id: package_id_for(diagram_row),
27
27
  diagram_type: diagram_row.diagram_type,
28
28
  bounds: bounds_for(diagram_row),
29
+ style: diagram_row.pdata,
30
+ style_ex: diagram_row.styleex,
31
+ show_package_contents: package_contents_enabled?(diagram_row),
32
+ hand_draw: hand_draw_enabled?(diagram_row),
33
+ show_notes: show_notes_enabled?(diagram_row),
34
+ show_parents: show_parents_enabled?(diagram_row),
29
35
  elements: build_elements(diagram_row),
30
36
  connectors: build_connectors(diagram_row),
31
37
  annotations: AnnotationBuilder.from_note(diagram_row.notes,
@@ -34,6 +40,42 @@ module Ea
34
40
  )
35
41
  end
36
42
 
43
+ # EA encodes "show package contents" as a non-zero integer in
44
+ # t_diagram.ShowPackageContents (1 in basic.qea, 255 in
45
+ # test.qea). When true, the package body lists its child
46
+ # classifiers/sub-packages as "+ Name" rows.
47
+ def package_contents_enabled?(diagram_row)
48
+ return false if diagram_row.showpackagecontents.nil?
49
+
50
+ diagram_row.showpackagecontents.to_i.nonzero?
51
+ end
52
+
53
+ # HandDraw=1 in t_diagram.StyleEx triggers EA's wavy
54
+ # hand-drawn border style for every element on the diagram.
55
+ def hand_draw_enabled?(diagram_row)
56
+ style_ex = diagram_row.styleex.to_s
57
+ match = style_ex.match(/HandDraw=(\d)/)
58
+ match && match[1] == "1"
59
+ end
60
+
61
+ # ShowNotes=1 in t_diagram.StyleEx renders the classifier's
62
+ # documentation note as wrapped text in the element body.
63
+ def show_notes_enabled?(diagram_row)
64
+ style_ex = diagram_row.styleex.to_s
65
+ match = style_ex.match(/ShowNotes=(\d)/)
66
+ match && match[1] == "1"
67
+ end
68
+
69
+ # HideParents=1 in t_diagram.PDATA suppresses EA's "off-canvas
70
+ # parent class" ghost line — the italic parent name that EA
71
+ # normally prepends to a placed element's header when its
72
+ # generalization parent is not placed on the diagram.
73
+ def show_parents_enabled?(diagram_row)
74
+ pdata = diagram_row.pdata.to_s
75
+ match = pdata.match(/HideParents=(\d)/)
76
+ match ? match[1] != "1" : true
77
+ end
78
+
37
79
  private
38
80
 
39
81
  def build_elements(diagram_row)
@@ -42,41 +84,245 @@ module Ea
42
84
  end
43
85
 
44
86
  def build_element(obj_row, diagram_row)
87
+ style_hash = DiagramStyleParser.parse(obj_row.objectstyle)
45
88
  Ea::Model::DiagramElement.new(
46
89
  id: IdNormalizer.synthetic("de", diagram_row.diagram_id,
47
90
  obj_row.instance_id),
48
91
  diagram_id: IdNormalizer.from_guid(diagram_row.ea_guid),
49
92
  model_element_ref: ref_for_object(obj_row),
50
93
  bounds: bounds_from_rect(obj_row),
51
- style: DiagramStyleParser.parse(obj_row.objectstyle)
94
+ style: style_hash,
95
+ font_family: style_hash[:font],
96
+ font_bold: truthy?(style_hash[:bold]),
97
+ font_italic: truthy?(style_hash[:italic]),
98
+ show_tagged_values: truthy?(style_hash[:tag])
52
99
  )
53
100
  end
54
101
 
102
+ def truthy?(raw)
103
+ raw == "1" || raw == "-1"
104
+ end
105
+
55
106
  def build_connectors(diagram_row)
56
107
  links = database.diagram_links_for(diagram_row.diagram_id) || []
57
- links.map { |link_row| build_connector(link_row, diagram_row) }
108
+ explicit = links.map { |link_row| build_connector(link_row, diagram_row) }
109
+ phantom = phantom_connectors(diagram_row, explicit)
110
+ explicit + phantom
111
+ end
112
+
113
+ # EA renders PHANTOM CONNECTORS for associations that exist
114
+ # in t_connector between two elements BOTH placed on the
115
+ # current diagram, even when no t_diagramlinks entry exists
116
+ # for that connector on this diagram. The phantom connector
117
+ # renders as a straight line between the two element edges,
118
+ # with SourceRole labels and appropriate markers.
119
+ #
120
+ # Only Association and Aggregation types produce phantom
121
+ # connectors. Generalization, Dependency, and Package types
122
+ # do NOT get phantom rendering.
123
+ PHANTOM_CONNECTOR_TYPES = %w[Association Aggregation].freeze
124
+
125
+ def phantom_connectors(diagram_row, explicit_connectors)
126
+ placed_ids = placed_object_ids_on(diagram_row.diagram_id)
127
+ explicit_conn_ids = explicit_connectors.map(&:relationship_ref).compact.to_set
128
+
129
+ all_connectors = database.collections[:connectors] || []
130
+ all_connectors.filter_map.with_index do |conn, idx|
131
+ next nil unless PHANTOM_CONNECTOR_TYPES.include?(conn.connector_type)
132
+ next nil unless placed_ids.include?(conn.start_object_id.to_i)
133
+ next nil unless placed_ids.include?(conn.end_object_id.to_i)
134
+
135
+ ref = ref_for_raw_connector(conn)
136
+ next nil if explicit_conn_ids.include?(ref)
137
+
138
+ build_phantom_connector(conn, diagram_row, idx)
139
+ end
140
+ end
141
+
142
+ def ref_for_raw_connector(conn)
143
+ IdNormalizer.from_guid(conn.ea_guid)
144
+ end
145
+
146
+ # Default label geometry for phantom connectors. EA renders
147
+ # SourceRole + «property» + multiplicity at default offsets
148
+ # from the source endpoint when no explicit geometry exists.
149
+ DEFAULT_LABEL_BOXES = {
150
+ llb: { "ox" => 0, "oy" => 0 },
151
+ llt: { "ox" => 0, "oy" => 0 }
152
+ }.freeze
153
+
154
+ def build_phantom_connector(conn, diagram_row, idx)
155
+ source_placement = diagram_object_placement(diagram_row.diagram_id,
156
+ conn.start_object_id)
157
+ target_placement = diagram_object_placement(diagram_row.diagram_id,
158
+ conn.end_object_id)
159
+ return nil unless source_placement && target_placement
160
+
161
+ source_bounds = bounds_from_rect(source_placement)
162
+ target_bounds = bounds_from_rect(target_placement)
163
+ source_point = bounds_edge_point(source_bounds, target_bounds)
164
+ target_point = bounds_edge_point(target_bounds, source_bounds)
165
+
166
+ waypoints = [
167
+ Ea::Model::Waypoint.new(position: Ea::Model::Point.new(x: source_point[0], y: source_point[1])),
168
+ Ea::Model::Waypoint.new(position: Ea::Model::Point.new(x: target_point[0], y: target_point[1]))
169
+ ]
170
+
171
+ Ea::Model::DiagramConnector.new(
172
+ id: IdNormalizer.synthetic("pc", diagram_row.diagram_id, idx),
173
+ diagram_id: IdNormalizer.from_guid(diagram_row.ea_guid),
174
+ relationship_ref: ref_for_raw_connector(conn),
175
+ connector_type: conn.connector_type,
176
+ direction: conn.direction,
177
+ waypoints: waypoints,
178
+ label_boxes: DEFAULT_LABEL_BOXES,
179
+ style: {},
180
+ hidden: false,
181
+ has_geometry_offsets: false
182
+ )
183
+ end
184
+
185
+ # Computes the edge attachment point on source bounds that
186
+ # faces the target bounds. Uses the center-to-center direction
187
+ # to pick the nearest edge intersection.
188
+ def bounds_edge_point(bounds, other_bounds)
189
+ cx = bounds.x + bounds.width / 2
190
+ cy = bounds.y + bounds.height / 2
191
+ ocx = other_bounds.x + other_bounds.width / 2
192
+ ocy = other_bounds.y + other_bounds.height / 2
193
+
194
+ dx = ocx - cx
195
+ dy = ocy - cy
196
+
197
+ if dx.abs * bounds.height > dy.abs * bounds.width
198
+ x = dx > 0 ? bounds.x + bounds.width : bounds.x
199
+ y = cy + dy * (x - cx).abs / dx.abs rescue cy
200
+ [x, y]
201
+ else
202
+ y = dy > 0 ? bounds.y + bounds.height : bounds.y
203
+ x = cx + dx * (y - cy).abs / dy.abs rescue cx
204
+ [x, y]
205
+ end
58
206
  end
59
207
 
60
208
  def build_connector(link_row, diagram_row)
209
+ connector = database.find_connector(link_row.connectorid)
210
+ geom = parse_geometry_fields(link_row.geometry)
61
211
  Ea::Model::DiagramConnector.new(
62
212
  id: IdNormalizer.synthetic("dc", diagram_row.diagram_id,
63
213
  link_row.instance_id),
64
214
  diagram_id: IdNormalizer.from_guid(diagram_row.ea_guid),
65
215
  relationship_ref: ref_for_connector(link_row),
216
+ connector_type: connector&.connector_type,
217
+ direction: connector&.direction,
66
218
  waypoints: waypoints_for_link(link_row, diagram_row),
67
- style: DiagramStyleParser.parse(link_row.style)
219
+ label_boxes: geom[:label_boxes] || {},
220
+ style: DiagramStyleParser.parse(link_row.style),
221
+ hidden: hidden?(link_row),
222
+ ghost_labels: ghost_labels_for(connector, diagram_row, link_row),
223
+ has_geometry_offsets: geometry_has_offsets?(link_row.geometry)
68
224
  )
69
225
  end
70
226
 
71
- # EA's t_diagramlinks.Geometry field stores connector routing
72
- # in a packed format. The leading "X1,Y1,X2,Y2" pair (when
73
- # present) is in a coordinate frame that does NOT match the
74
- # element rect frame (elements use RectTop < RectBottom with
75
- # negative y; geometry uses positive y). Rather than trust
76
- # the stored absolute coords, we compute the polyline from
77
- # the actual placed-element bounds + the SX/SY/EX/EY delta
78
- # fields, which describe the bend routing relative to the
79
- # element edges.
227
+ # EA's t_diagramlinks.Geometry may carry explicit SX/SY/EX/EY
228
+ # offset fields (even zeros) OR omit them entirely. The
229
+ # presence of SX= gates the «import» package_anchor marker:
230
+ # basic.qea Package Imports has SX=0;SY=0 marker renders;
231
+ # plateau Urban Planning ADE2 omits SX/SY no marker.
232
+ def geometry_has_offsets?(geometry_str)
233
+ return false if geometry_str.nil? || geometry_str.empty?
234
+
235
+ geometry_str.to_s.include?("SX=")
236
+ end
237
+
238
+ # EA's t_diagramlinks.Hidden is 1 when the user has hidden
239
+ # the connector on this diagram (right-click → Hide). Hidden
240
+ # connectors are absent from the reference SVG output.
241
+ def hidden?(link_row)
242
+ link_row.hidden.to_i.nonzero?
243
+ end
244
+
245
+ # Builds GhostLabel entries for connector endpoints that
246
+ # reference objects NOT placed on the current diagram. EA
247
+ # renders the off-canvas classifier's name as italic text
248
+ # near the connector's on-canvas endpoint so the reader
249
+ # knows what the connector attaches to outside the diagram.
250
+ def ghost_labels_for(connector, diagram_row, link_row)
251
+ return [] unless connector
252
+
253
+ placed_ids = placed_object_ids_on(diagram_row.diagram_id)
254
+ ghosts = []
255
+ %i[source target].each do |end_kind|
256
+ ea_object_id = end_kind == :source ? connector.start_object_id : connector.end_object_id
257
+ next unless ea_object_id&.to_i&.positive?
258
+ next if placed_ids.include?(ea_object_id.to_i)
259
+
260
+ obj = database.find_object(ea_object_id.to_i)
261
+ next unless obj
262
+ next if obj.name.to_s.strip.empty?
263
+
264
+ anchor_point = ghost_anchor(end_kind, connector, link_row, diagram_row)
265
+ next unless anchor_point
266
+
267
+ ghosts << Ea::Model::GhostLabel.new(
268
+ id: IdNormalizer.synthetic("ghost", link_row.instance_id, ea_object_id.to_i),
269
+ name: obj.name.to_s,
270
+ end_kind: end_kind.to_s,
271
+ anchor_x: anchor_point[0],
272
+ anchor_y: anchor_point[1]
273
+ )
274
+ end
275
+ ghosts
276
+ end
277
+
278
+ # Returns the Array of EA object IDs that have a placement
279
+ # (t_diagramobjects row) on the given diagram.
280
+ def placed_object_ids_on(diagram_id)
281
+ objects = database.diagram_objects_for(diagram_id) || []
282
+ objects.map(&:ea_object_id)
283
+ end
284
+
285
+ # Ghost label anchors at the on-canvas end of the connector.
286
+ # When the source is off-canvas, anchor at the target's
287
+ # first waypoint; vice versa for target off-canvas.
288
+ def ghost_anchor(end_kind, connector, link_row, diagram_row)
289
+ placement = if end_kind == :source
290
+ target_placement_for(connector, diagram_row)
291
+ else
292
+ source_placement_for(connector, diagram_row)
293
+ end
294
+ return nil unless placement
295
+
296
+ # Use the on-canvas end's edge center as the anchor.
297
+ b = bounds_from_rect(placement)
298
+ # Offset to the right of the element so the label sits
299
+ # outside the on-canvas element. EA's actual offset is
300
+ # roughly +12px in x and -8px in y from the element's
301
+ # right-center edge.
302
+ [b.x + b.width + 4, b.y + (b.height / 2) - 8]
303
+ end
304
+
305
+ def source_placement_for(connector, diagram_row)
306
+ diagram_object_placement(diagram_row.diagram_id, connector.start_object_id)
307
+ end
308
+
309
+ def target_placement_for(connector, diagram_row)
310
+ diagram_object_placement(diagram_row.diagram_id, connector.end_object_id)
311
+ end
312
+
313
+ # EA stores connector routing in two places:
314
+ #
315
+ # - t_diagramlinks.Path: explicit intermediate waypoints as
316
+ # `x:y;x:y;...` in the element-rect coordinate frame.
317
+ # Present when the user has manually routed the connector
318
+ # or when EA's auto-router produced multi-bend paths.
319
+ # - t_diagramlinks.Geometry SX/SY/EX/EY: delta fields that
320
+ # describe the source-side and target-side bend offsets
321
+ # relative to the edge attachment points.
322
+ #
323
+ # Path takes precedence when present — it's the authoritative
324
+ # routing. SX/SY/EX/EY are fallback for auto-routed direct
325
+ # connectors with at most one bend on each end.
80
326
  #
81
327
  # If we can't resolve the source or target element placement
82
328
  # for this diagram, we emit no waypoints (the connector will
@@ -96,21 +342,40 @@ module Ea
96
342
 
97
343
  source_point = element_edge_point(source_placement, :source, edge_out)
98
344
  target_point = element_edge_point(target_placement, :target, edge_out)
345
+
346
+ intermediate = intermediate_waypoints(link_row)
347
+ if intermediate.any?
348
+ points = [source_point, *intermediate, target_point]
349
+ else
350
+ points = sx_sy_ex_ey_waypoints(source_point, target_point, geom)
351
+ end
352
+
353
+ points.map do |x, y|
354
+ Ea::Model::Waypoint.new(position: Ea::Model::Point.new(x: x, y: y))
355
+ end
356
+ end
357
+
358
+ # Parse t_diagramlinks.Path into [[x, y], ...] intermediate
359
+ # waypoints. Format: `x1:y1;x2:y2;...`. Empty string returns [].
360
+ def intermediate_waypoints(link_row)
361
+ raw = link_row.path.to_s
362
+ return [] if raw.empty?
363
+
364
+ raw.scan(/(-?\d+):(-?\d+)/).map { |x, y| [x.to_i, y.to_i] }
365
+ end
366
+
367
+ # Fallback when no explicit Path: compute source/target bend
368
+ # points from the SX/SY/EX/EY delta fields.
369
+ def sx_sy_ex_ey_waypoints(source_point, target_point, geom)
99
370
  points = [source_point]
100
- # SX/SY: delta from source point to first bend.
101
371
  if geom[:sx] && geom[:sy] && (geom[:sx].nonzero? || geom[:sy].nonzero?)
102
372
  points << [source_point[0] + geom[:sx], source_point[1] + geom[:sy]]
103
373
  end
104
- # EX/EY: delta from second bend to target point. Reverse
105
- # to get from target back to the bend.
106
374
  if geom[:ex] && geom[:ey] && (geom[:ex].nonzero? || geom[:ey].nonzero?)
107
375
  points << [target_point[0] - geom[:ex], target_point[1] - geom[:ey]]
108
376
  end
109
377
  points << target_point
110
-
111
- points.map do |x, y|
112
- Ea::Model::Waypoint.new(position: Ea::Model::Point.new(x: x, y: y))
113
- end
378
+ points
114
379
  end
115
380
 
116
381
  # Parse the SX, SY, EX, EY, EDGE key=value pairs out of the
@@ -126,7 +391,61 @@ module Ea
126
391
  sy: pick_int(s, /SY=(-?\d+)/),
127
392
  ex: pick_int(s, /EX=(-?\d+)/),
128
393
  ey: pick_int(s, /EY=(-?\d+)/),
129
- edge: pick_int(s, /EDGE=(-?\d+)/)
394
+ edge: pick_int(s, /EDGE=(-?\d+)/),
395
+ label_boxes: parse_label_boxes(s)
396
+ }
397
+ end
398
+
399
+ # Parse EA's $LLB=, $LLT=, $LRB=, $LRT= label box geometry.
400
+ # Format: $LLB=CX=<int>:CY=<int>:OX=<int>:OY=<int>:HDN=0:BLD=0:
401
+ # ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:ROT=0;
402
+ # Only the FIRST segment carries the leading `$`; subsequent
403
+ # segments use bare `KEY=` after the semicolon separator.
404
+ #
405
+ # Returns Hash keyed by :llb, :llt, :lrt, :lrb (and optional
406
+ # :lmt, :lmb, :irhs, :ilhs). Each value is a Hash with:
407
+ # - "ox", "oy" — label offset from anchor (always set)
408
+ # - "cx", "cy" — cell size for label box
409
+ # - "hidden" — bool, label suppressed when true
410
+ # - "bold", "italic", "underline" — bool font flags
411
+ # - "color" — signed int (VB OLE_COLOR; -1 = default)
412
+ # - "alignment" — 0=left, 1=center, 2=right
413
+ # - "direction" — text direction enum
414
+ # - "rotation" — degrees
415
+ def parse_label_boxes(geom_str)
416
+ boxes = {}
417
+ %i[llb llt lrt lrb lmt lmb irhs ilhs].each do |key|
418
+ match = geom_str.match(/(?:\$|;)\s*#{key.to_s.upcase}=([^;]*)/i)
419
+ next unless match && !match[1].empty?
420
+
421
+ parsed = parse_label_style(match[1])
422
+ next unless parsed
423
+
424
+ boxes[key] = parsed
425
+ end
426
+ boxes
427
+ end
428
+
429
+ # Parse one label box's colon-separated key=value body into
430
+ # a Hash with string keys. Returns nil if OX/OY are absent.
431
+ def parse_label_style(body)
432
+ ox = body.match(/OX=(-?\d+)/)
433
+ oy = body.match(/OY=(-?\d+)/)
434
+ return nil unless ox && oy
435
+
436
+ {
437
+ "ox" => ox[1].to_i,
438
+ "oy" => oy[1].to_i,
439
+ "cx" => pick_int(body, /CX=(-?\d+)/),
440
+ "cy" => pick_int(body, /CY=(-?\d+)/),
441
+ "hidden" => pick_int(body, /HDN=(\d+)/) == 1,
442
+ "bold" => pick_int(body, /BLD=(\d+)/) == 1,
443
+ "italic" => pick_int(body, /ITA=(\d+)/) == 1,
444
+ "underline" => pick_int(body, /UND=(\d+)/) == 1,
445
+ "color" => pick_int(body, /CLR=(-?\d+)/),
446
+ "alignment" => pick_int(body, /ALN=(\d+)/),
447
+ "direction" => pick_int(body, /DIR=(\d+)/),
448
+ "rotation" => pick_int(body, /ROT=(-?\d+)/)
130
449
  }
131
450
  end
132
451
 
@@ -186,12 +505,21 @@ module Ea
186
505
  )
187
506
  end
188
507
 
508
+ # EA's t_diagramobjects stores rect with the origin at the
509
+ # bottom-left (math convention) — recttop > rectbottom for
510
+ # valid bounds, and y values are often negative. Convert to
511
+ # screen-space (origin top-left) by taking min/max and using
512
+ # absolute width/height.
189
513
  def bounds_from_rect(obj_row)
514
+ left = obj_row.rectleft || 0
515
+ right = obj_row.rectright || 0
516
+ top = obj_row.recttop || 0
517
+ bottom = obj_row.rectbottom || 0
190
518
  Ea::Model::Bounds.new(
191
- x: obj_row.rectleft || 0,
192
- y: obj_row.recttop || 0,
193
- width: (obj_row.rectright || 0) - (obj_row.rectleft || 0),
194
- height: (obj_row.rectbottom || 0) - (obj_row.recttop || 0)
519
+ x: [left, right].min,
520
+ y: [top, bottom].min,
521
+ width: (right - left).abs,
522
+ height: (bottom - top).abs
195
523
  )
196
524
  end
197
525
 
@@ -5,7 +5,12 @@ module Ea
5
5
  module Qea
6
6
  # Normalizes EA's identifier formats into stable strings used
7
7
  # as Ea::Model element ids. EA represents identity as
8
- # `{GUID}` strings. We strip the braces to give the bare GUID.
8
+ # `{GUID}` strings.
9
+ #
10
+ # Two output formats:
11
+ # - `from_guid` — bare GUID without braces (canonical model id)
12
+ # - `to_eaid` — `EAID_<guid with dashes-to-underscores>` matching
13
+ # the reference SVG filename convention
9
14
  module IdNormalizer
10
15
  module_function
11
16
 
@@ -15,6 +20,15 @@ module Ea
15
20
  ea_guid.to_s.gsub(/[{}]/, "")
16
21
  end
17
22
 
23
+ # Convert a GUID to the EAID_ filename format used by EA's
24
+ # SVG export: strip braces, replace dashes with underscores,
25
+ # prefix with EAID_.
26
+ def to_eaid(ea_guid)
27
+ return nil if ea_guid.nil? || ea_guid.empty?
28
+
29
+ "EAID_" + ea_guid.to_s.gsub(/[{}]/, "").tr("-", "_")
30
+ end
31
+
18
32
  def synthetic(prefix, *parts)
19
33
  "#{prefix}:#{parts.join(":")}"
20
34
  end
@@ -22,3 +36,4 @@ module Ea
22
36
  end
23
37
  end
24
38
  end
39
+
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Sources
5
+ module Qea
6
+ # Translates EA t_object rows of Object_Type="Object" into
7
+ # Ea::Model::InstanceSpecification instances. Slots are parsed
8
+ # from the RunState field, which uses a packed format:
9
+ #
10
+ # @VAR;Variable=name;Value=val;Op===;@ENDVAR;@VAR;...
11
+ #
12
+ # Each @VAR...@ENDVAR block is one Slot.
13
+ class InstanceBuilder
14
+ RUN_STATE_PATTERN = /
15
+ @VAR;
16
+ (?:Variable=(?<name>[^;]*);)?
17
+ (?:Value=(?<value>[^;]*);)?
18
+ (?:Op=(?<op>[^;]*);)?
19
+ @ENDVAR;
20
+ /x.freeze
21
+
22
+ attr_reader :database
23
+
24
+ def initialize(database)
25
+ @database = database
26
+ end
27
+
28
+ def build_all
29
+ objects = database.collections[:objects] || []
30
+ objects.filter_map { |row| build_one(row) if instance?(row) }
31
+ end
32
+
33
+ def build_one(row)
34
+ Ea::Model::InstanceSpecification.new(
35
+ id: IdNormalizer.from_guid(row.ea_guid),
36
+ name: row.name.to_s,
37
+ classifier_id: classifier_id_for(row),
38
+ classifier_name: classifier_name_for(row),
39
+ package_id: package_id_for(row),
40
+ package_name: package_name_for(row),
41
+ qualified_name: qualified_name_for(row),
42
+ slots: slots_for(row),
43
+ annotations: AnnotationBuilder.from_note(row.note, row.ea_guid,
44
+ kind: "documentation")
45
+ )
46
+ end
47
+
48
+ private
49
+
50
+ def instance?(row)
51
+ row.object_type == "Object"
52
+ end
53
+
54
+ def classifier_id_for(row)
55
+ classifier = database.find_object(row.classifier.to_i) if row.classifier&.to_i&.positive?
56
+ return nil unless classifier
57
+
58
+ IdNormalizer.from_guid(classifier.ea_guid)
59
+ end
60
+
61
+ def classifier_name_for(row)
62
+ classifier = database.find_object(row.classifier.to_i) if row.classifier&.to_i&.positive?
63
+ return nil unless classifier
64
+
65
+ classifier.name.to_s
66
+ end
67
+
68
+ def package_id_for(row)
69
+ return nil unless row.package_id&.to_i&.positive?
70
+
71
+ pkg = database.find_package(row.package_id.to_i)
72
+ return nil unless pkg
73
+
74
+ IdNormalizer.from_guid(pkg.ea_guid)
75
+ end
76
+
77
+ def package_name_for(row)
78
+ return nil unless row.package_id&.to_i&.positive?
79
+
80
+ pkg = database.find_package(row.package_id.to_i)
81
+ pkg&.name
82
+ end
83
+
84
+ # EA's t_object.Name may carry a qualified name with a ":"
85
+ # prefix (e.g. "gml:CodeType"). When the instance is in a
86
+ # nested package the qualified name uses the package path.
87
+ def qualified_name_for(row)
88
+ name = row.name.to_s
89
+ return name if name.empty?
90
+
91
+ pkg_name = package_name_for(row)
92
+ pkg_name ? "#{pkg_name}::#{name}" : name
93
+ end
94
+
95
+ # Parse the RunState field into Slot instances. Each
96
+ # @VAR;Variable=X;Value=Y;Op=Z;@ENDVAR; is one slot.
97
+ # When Op is absent we default to "=".
98
+ def slots_for(row)
99
+ run_state = row.runstate.to_s
100
+ return [] if run_state.empty?
101
+
102
+ run_state.scan(RUN_STATE_PATTERN).map do |(name, value, op)|
103
+ Ea::Model::Slot.new(
104
+ name: name.to_s,
105
+ value: value.to_s,
106
+ op: (op.to_s.empty? ? "=" : op)
107
+ )
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end