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
@@ -4,40 +4,64 @@ module Ea
4
4
  module Svg
5
5
  module EaEmitter
6
6
  # Top-level orchestrator: emits a complete standalone SVG
7
- # document matching EA's structure. Calls the layer emitters
8
- # in EA's canonical order.
7
+ # document matching EA's encoding. Bound computation and
8
+ # layer sequencing delegated to BoundsCalculator and
9
+ # LayerSequencer respectively (SRP).
9
10
  class Document
10
- BUILD_ID = "1628"
11
+ BUILD_ID = "1624"
11
12
 
12
- attr_reader :diagram, :model_index, :options
13
+ attr_reader :diagram, :model_index, :frame, :document
13
14
 
14
- def initialize(diagram, model_index:, **_options)
15
+ def initialize(diagram, model_index:, frame: true, document: nil, **_options)
15
16
  @diagram = diagram
16
17
  @model_index = model_index
18
+ @frame = frame
19
+ @document = document
17
20
  end
18
21
 
19
22
  def render
20
- canvas = Canvas.from(diagram)
21
- layers = [
22
- Background.render(canvas),
23
- Elements.new(diagram, model_index: model_index).render,
24
- Connectors.new(diagram).render,
25
- Markers.new(diagram, model_index: model_index).render,
26
- Labels.new(diagram).render
27
- ].reject { |s| s.nil? || s.empty? }
28
-
29
- <<~SVG
30
- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
31
- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
32
-
33
- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="#{canvas.width_cm}" height="#{canvas.height_cm}" viewBox="#{canvas.view_box}">
34
- <title></title>
35
- <desc>Created with Enterprise Architect (Build: #{BUILD_ID}) 2</desc>
36
-
37
- #{layers.join("\n\n")}
38
- </svg>
39
- SVG
23
+ canvas = Canvas.from(diagram, model_index: model_index)
24
+ layers = LayerSequencer.new(diagram, model_index: model_index,
25
+ canvas: canvas, frame: frame,
26
+ document: document)
27
+ .layers
28
+ .reject { |s| s.nil? || s.empty? }
29
+ image_layer = emit_images
30
+ layers << image_layer if image_layer
31
+
32
+ %(<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">\n\n<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="#{canvas.width_cm}" height="#{canvas.height_cm}" viewBox="#{canvas.view_box}">\n<title></title>\n<desc>Created with Enterprise Architect (Build: #{BUILD_ID}) 2</desc>\n#{layers.join("\n")}\n</svg>)
33
+ end
34
+
35
+ # Walks t_image rows (when the document carries a Database
36
+ # with an :images collection) and invokes EmfRenderer on
37
+ # each. Emits an `<image>` element per successfully converted
38
+ # SVG, or returns nil when no images or all conversions fail.
39
+ #
40
+ # The emfsvg gem currently can't parse EA's specific EMF
41
+ # variant (see TODO.diagrams/87 + TODO.complete/56). Until
42
+ # upstream emfsvg supports it, this method returns nil
43
+ # gracefully. The wiring is correct infrastructure for when
44
+ # emfsvg catches up.
45
+ def emit_images
46
+ return nil unless document
47
+ return nil unless document.is_a?(Ea::Qea::Database)
48
+
49
+ images = document.collections[:images] || []
50
+ return nil if images.empty?
51
+
52
+ fragments = images.filter_map { |image| render_image(image) }
53
+ return nil if fragments.empty?
54
+
55
+ %(<g id="images">\n#{fragments.join("\n")}\n</g>)
56
+ end
57
+
58
+ def render_image(image)
59
+ svg = Ea::Image::EmfRenderer.render(image.bytes)
60
+ return nil unless svg
61
+
62
+ %(<g>#{svg}</g>)
40
63
  end
64
+ private :render_image
41
65
  end
42
66
  end
43
67
  end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Builds the text representation of a single property line
8
+ # for the attribute compartment. Encapsulates the four EA
9
+ # rendering rules:
10
+ #
11
+ # 1. Visibility marker (+, -, #, ~).
12
+ # 2. Namespace prefix on properties inherited from a
13
+ # classifier in a different package ("core::mimeType").
14
+ # 3. Primitive type name stripping ("xs::double" → "double").
15
+ # 4. Multiplicity shorthand ("[2..2]" → "[2]") and
16
+ # `{ordered}` modifier.
17
+ #
18
+ # Stateless: each call returns a fresh line String. Pass a
19
+ # `Lookup` to enable cross-classifier resolution (namespace
20
+ # prefix). Without a lookup, the line is rendered as-is
21
+ # (backwards-compatible).
22
+ class AttributeLineBuilder
23
+ # XSD primitive types whose namespace prefix EA drops in
24
+ # the rendered attribute line. The full set is not yet
25
+ # reverse-engineered — leave empty for now so we never
26
+ # wrongly strip a prefix. TODO-D 47 will populate this
27
+ # once verified per-diagram.
28
+ PRIMITIVE_TYPES = Set.new([]).freeze
29
+
30
+ attr_reader :property, :host, :lookup
31
+
32
+ # property - the Ea::Model::Property to render
33
+ # host - the Ea::Model::Classifier whose compartment
34
+ # we're rendering (determines "inherited" status)
35
+ # lookup - object responding to `#call(id) -> Classifier`
36
+ # used to resolve the property's declaring
37
+ # classifier (defaults to no lookup).
38
+ def initialize(property, host: nil, lookup: nil)
39
+ @property = property
40
+ @host = host
41
+ @lookup = lookup
42
+ end
43
+
44
+ def to_s
45
+ parts = ["#{visibility_marker} "]
46
+ parts << namespaced_name
47
+ type_str = formatted_type
48
+ parts << ": #{type_str}" unless type_str.empty?
49
+ mult = multiplicity
50
+ parts << " #{mult}" unless mult.empty?
51
+ parts << " = #{property.default_value}" if property.default_value && !property.default_value.to_s.empty?
52
+ parts.join.rstrip
53
+ end
54
+
55
+ private
56
+
57
+ def visibility_marker
58
+ case property.visibility
59
+ when "private" then "-"
60
+ when "protected" then "#"
61
+ when "package" then "~"
62
+ else "+"
63
+ end
64
+ end
65
+
66
+ # Property name, prefixed with the declaring classifier's
67
+ # package name when the property is inherited from a
68
+ # different package than the host classifier.
69
+ def namespaced_name
70
+ prefix = inherited_namespace_prefix
71
+ base = property.name.to_s
72
+ prefix && !prefix.empty? ? "#{prefix}::#{base}" : base
73
+ end
74
+
75
+ # Returns the declaring classifier's package name when it
76
+ # differs from the host's package; nil otherwise.
77
+ def inherited_namespace_prefix
78
+ return nil unless host && lookup
79
+ return nil if property.owner_id.nil? || property.owner_id == host.id
80
+
81
+ owner = lookup.call(property.owner_id)
82
+ return nil unless owner.is_a?(Ea::Model::Classifier)
83
+ return nil if owner.package_id.nil?
84
+ return nil if owner.package_id == host.package_id
85
+
86
+ owner.package_name
87
+ end
88
+
89
+ # Format the type name. Primitive types from the PRIMITIVE_TYPES
90
+ # set render bare; everything else renders verbatim from the
91
+ # source. The source already provides the correct colon form
92
+ # (single for XSD, double for UML package qualifiers).
93
+ def formatted_type
94
+ raw = property.type_name.to_s
95
+ return "" if raw.empty?
96
+
97
+ simple = raw.split(":").last.to_s.split("::").last.to_s
98
+ PRIMITIVE_TYPES.member?(simple) ? simple : raw
99
+ end
100
+
101
+ def multiplicity
102
+ lower = property.multiplicity_lower
103
+ upper = property.multiplicity_upper
104
+ return "" if lower.nil? && upper.nil?
105
+ return "" if lower == 1 && upper == 1
106
+
107
+ "[#{lower || 0}..#{bound_text(upper)}]"
108
+ end
109
+
110
+ def bound_text(value)
111
+ value == -1 ? "*" : value.to_s
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Emits the attribute compartment text `<g>`. Each attribute
8
+ # renders as TWO `<text>` elements (visibility marker +
9
+ # content) matching EA's encoding.
10
+ class AttributeRenderer
11
+ DEFAULT_VISIBILITY_X_OFFSET = 5
12
+ DEFAULT_CONTENT_X_OFFSET = 26
13
+ DEFAULT_FONT_UNIT = "pt"
14
+
15
+ def self.render(lines, bounds:, first_y:, family:,
16
+ size:, size_unit: DEFAULT_FONT_UNIT,
17
+ fill: "#000000",
18
+ visibility_x_offset: DEFAULT_VISIBILITY_X_OFFSET,
19
+ content_x_offset: DEFAULT_CONTENT_X_OFFSET)
20
+ line_h = size + 4
21
+ text_blocks = []
22
+ lines.each_with_index do |line, idx|
23
+ y = first_y + (idx * line_h)
24
+ visibility, rest = split_visibility(line)
25
+ if visibility
26
+ text_blocks << build_text(bounds.x + visibility_x_offset, y, visibility, family, size, size_unit, fill)
27
+ text_blocks << build_text(bounds.x + content_x_offset, y, rest, family, size, size_unit, fill)
28
+ else
29
+ text_blocks << build_text(bounds.x + visibility_x_offset, y, line.strip, family, size, size_unit, fill)
30
+ end
31
+ end
32
+ group_style = "stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel; fill:#{fill};fill-opacity:1.00; stroke:#000000; stroke-opacity:0.00"
33
+ %(<g style="#{group_style}">\n#{text_blocks.join("\n")}\n</g>)
34
+ end
35
+
36
+ # Builds attribute display lines from classifier properties,
37
+ # hiding properties that are navigable association ends
38
+ # (those are rendered as connector lines).
39
+ #
40
+ # When a property carries a stereotype (e.g. «voidable»),
41
+ # EA renders the stereotype label as a separate line above
42
+ # the property's own line. We insert those labels here so
43
+ # the renderer's per-line spacing naturally places them.
44
+ #
45
+ # Pass a `lookup` (any callable returning a Classifier for
46
+ # an id) to enable inherited-property namespace prefixing.
47
+ def self.lines_for(classifier, lookup: nil)
48
+ props = displayable_properties(classifier)
49
+ return [] unless props
50
+
51
+ props.flat_map do |prop|
52
+ lines = []
53
+ stereotype = property_stereotype(prop)
54
+ lines << "«#{stereotype}»" if stereotype
55
+ lines << AttributeLineBuilder.new(prop, host: classifier,
56
+ lookup: lookup).to_s
57
+ end
58
+ end
59
+
60
+ # Internal helpers
61
+
62
+ def self.split_visibility(line)
63
+ stripped = line.strip
64
+ return [nil, stripped] unless stripped.match?(/^[-+~#]\s/)
65
+
66
+ visibility = "#{stripped[0]} "
67
+ rest = stripped[2..].to_s.strip
68
+ [visibility, rest]
69
+ end
70
+ private_class_method :split_visibility
71
+
72
+ def self.build_text(x, y, content, family, size, size_unit = DEFAULT_FONT_UNIT, fill = "#000000")
73
+ TextRenderer.new(content: content, x: x, y: y,
74
+ family: family, size: size, size_unit: size_unit, fill: fill).to_svg
75
+ end
76
+ private_class_method :build_text
77
+
78
+ def self.displayable_properties(classifier)
79
+ return nil unless classifier.properties
80
+
81
+ classifier.properties.reject(&:association_id)
82
+ end
83
+ private_class_method :displayable_properties
84
+
85
+ # Returns the first stereotype ref for a property, or nil.
86
+ # Used to emit «voidable» (and similar) labels above the
87
+ # property's own line.
88
+ def self.property_stereotype(property)
89
+ refs = property.stereotype_refs
90
+ return nil unless refs&.any?
91
+
92
+ refs.first.to_s
93
+ end
94
+ private_class_method :property_stereotype
95
+
96
+ # EA renders namespace separators as "::" (UML standard)
97
+ # even when the source XMI stores them as ":" (XML style).
98
+ def self.namespace_double_colon(type_name)
99
+ return nil if type_name.nil? || type_name.empty?
100
+
101
+ type_name.to_s.gsub(/([A-Za-z0-9_]):([A-Za-z])/, '\1::\2')
102
+ end
103
+ private_class_method :namespace_double_colon
104
+
105
+ def self.multiplicity_text(property)
106
+ lower = property.multiplicity_lower
107
+ upper = property.multiplicity_upper
108
+ return "" if lower.nil? && upper.nil?
109
+ return "" if lower == 1 && upper == 1
110
+ return "[#{upper == -1 ? "*" : upper}]" if lower == upper
111
+
112
+ "[#{lower || 0}..#{upper == -1 ? "*" : upper}]"
113
+ end
114
+ private_class_method :multiplicity_text
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Decodes EA's BGR-encoded BCol/LCol integers into "#RRGGBB"
8
+ # hex strings. EA stores color as a Win32 COLORREF: R in low
9
+ # byte, G in mid byte, B in high byte.
10
+ #
11
+ # Returns nil when bgr_int is nil OR equals -1 (EA's sentinel
12
+ # for "no color override" — callers fall through to a
13
+ # stereotype-derived default).
14
+ class BColDecoder
15
+ SENTINEL = -1
16
+
17
+ def self.to_hex(bgr_int)
18
+ return nil if bgr_int.nil? || bgr_int == SENTINEL
19
+
20
+ r = bgr_int & 0xff
21
+ g = (bgr_int >> 8) & 0xff
22
+ b = (bgr_int >> 16) & 0xff
23
+ format("#%02X%02X%02X", r, g, b)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Y-coordinate calculator for the compartments of an element
8
+ # box: header text, header divider, attribute rows, operation
9
+ # rows, enum literals, tagged-value rows.
10
+ #
11
+ # Owns its own coordinate math; the rest of Elements only
12
+ # reads `header_first_y`, `divider_y`, `attr_first_y`, etc.
13
+ # Construction takes the bounds, font size, line counts,
14
+ # and per-region offsets from the theme.
15
+ #
16
+ # Extracted from Elements.rb to keep each concern in one
17
+ # place (MECE). Element rendering reads these values;
18
+ # coordinate math changes don't ripple into rendering.
19
+ class CompartmentGeometry
20
+ attr_reader :bounds, :size, :header_lines_count,
21
+ :attr_lines_count, :op_lines_count,
22
+ :tagged_values_count,
23
+ :header_top_padding, :header_line_offset,
24
+ :divider_offset, :attr_line_offset,
25
+ :attr_first_offset
26
+
27
+ def initialize(bounds:, size:, header_lines_count:,
28
+ attr_lines_count:, op_lines_count:,
29
+ tagged_values_count:,
30
+ header_top_padding: 9,
31
+ header_line_offset: 6,
32
+ divider_offset: 8,
33
+ attr_line_offset: 4,
34
+ attr_first_offset: 7)
35
+ @bounds = bounds
36
+ @size = size
37
+ @header_lines_count = header_lines_count
38
+ @attr_lines_count = attr_lines_count
39
+ @op_lines_count = op_lines_count
40
+ @tagged_values_count = tagged_values_count
41
+ @header_top_padding = header_top_padding
42
+ @header_line_offset = header_line_offset
43
+ @divider_offset = divider_offset
44
+ @attr_line_offset = attr_line_offset
45
+ @attr_first_offset = attr_first_offset
46
+ end
47
+
48
+ def header_first_y
49
+ bounds.y + size + (header_top_padding || 9)
50
+ end
51
+
52
+ # Y of the divider line between header and the rest.
53
+ # Returns nil when there's no header content to separate
54
+ # from empty below-header content.
55
+ def divider_y
56
+ return nil if header_lines_count.zero?
57
+
58
+ header_first_y +
59
+ ([header_lines_count, 1].max - 1) * (size + (header_line_offset || 6)) +
60
+ (divider_offset || 8)
61
+ end
62
+
63
+ def attr_first_y
64
+ return bounds.y + size + (header_top_padding || 9) + 12 unless divider_y
65
+
66
+ divider_y + size + (attr_first_offset || 7)
67
+ end
68
+
69
+ def attr_bottom_y
70
+ return attr_first_y unless attr_lines_count&.positive?
71
+
72
+ attr_first_y + (attr_lines_count - 1) * (size + (attr_line_offset || 4))
73
+ end
74
+
75
+ def op_divider_y
76
+ attr_bottom_y + size + 5
77
+ end
78
+
79
+ def op_first_y
80
+ return nil unless op_lines_count&.positive?
81
+
82
+ op_divider_y + size + 5
83
+ end
84
+
85
+ def op_bottom_y
86
+ return op_divider_y unless op_lines_count&.positive?
87
+
88
+ op_first_y + (op_lines_count - 1) * (size + 4)
89
+ end
90
+
91
+ def enum_divider_y
92
+ op_bottom_y + size + 5
93
+ end
94
+
95
+ def enum_literal_first_y
96
+ enum_divider_y + size + 5
97
+ end
98
+
99
+ # Tagged values appear after attributes (or after ops if
100
+ # present). EA does not emit a separate divider — the
101
+ # italic "tags" header marks the compartment.
102
+ def tagged_value_first_y
103
+ return nil unless tagged_values_count&.positive?
104
+
105
+ base = attr_bottom_y
106
+ base = op_bottom_y if op_lines_count&.positive?
107
+ base + size + 5
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Emits the constraints compartment text `<g>`. EA renders
8
+ # constraints as a sub-compartment with an italic "constraints"
9
+ # header followed by `{name}` lines:
10
+ #
11
+ # <text style="...font-style:italic;...">constraints</text>
12
+ # <text style="...font-style:normal;...">{pattern}</text>
13
+ class ConstraintRenderer
14
+ HEADER_TEXT = "constraints"
15
+ HEADER_X_OFFSET = 5
16
+ LINE_X_OFFSET = 5
17
+
18
+ def self.render(constraints, bounds:, first_y:, family:, size:,
19
+ fill: "#000000")
20
+ line_h = size + 4
21
+ text_blocks = []
22
+ text_blocks << build_text(
23
+ bounds.x + centered_x_offset(bounds, size), first_y,
24
+ HEADER_TEXT, family, size, fill, style: "italic"
25
+ )
26
+ constraints.each_with_index do |constraint, idx|
27
+ y = first_y + ((idx + 1) * line_h)
28
+ text_blocks << build_text(
29
+ bounds.x + LINE_X_OFFSET, y,
30
+ "{#{constraint.name}}", family, size, fill
31
+ )
32
+ end
33
+ %(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
34
+ end
35
+
36
+ def self.build_text(x, y, content, family, size, fill, style: "normal")
37
+ TextRenderer.new(content: content, x: x, y: y,
38
+ family: family, size: size, fill: fill,
39
+ style: style).to_svg
40
+ end
41
+ private_class_method :build_text
42
+
43
+ def self.centered_x_offset(bounds, size)
44
+ text_width = HEADER_TEXT.length * (size / 2)
45
+ centered = (bounds.width / 2 - text_width / 2).floor
46
+ [HEADER_X_OFFSET, centered].max
47
+ end
48
+ private_class_method :centered_x_offset
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Emits the horizontal divider path between header and
8
+ # attribute compartments.
9
+ class DividerRenderer
10
+ def self.render(bounds, y:, stroke:, stroke_width:)
11
+ %(<g style="stroke-width:#{stroke_width};stroke-linecap:round;stroke-linejoin:bevel; fill:#000000;fill-opacity:0.00; stroke:#{stroke}; stroke-opacity:1.00">\n <path d="M #{Canvas.coord(bounds.x)} #{Canvas.coord(y)} L #{Canvas.coord(bounds.x + bounds.width)} #{Canvas.coord(y)}" shape-rendering="auto"/>\n</g>)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Emits the enumeration literal compartment text `<g>`. Each
8
+ # literal appears as a separate text element matching EA's
9
+ # encoding:
10
+ #
11
+ # <text>literal_name</text>
12
+ #
13
+ # Rendered as a third compartment below the attribute
14
+ # compartment for Enumeration classifiers.
15
+ class EnumerationLiteralRenderer
16
+ def self.render(literals, bounds:, first_y:, family:, size:)
17
+ line_h = size + 4
18
+ text_blocks = literals.each_with_index.map do |literal, idx|
19
+ y = first_y + (idx * line_h)
20
+ build_text(bounds.x + 5, y, literal.name.to_s, family, size)
21
+ end
22
+ %(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
23
+ end
24
+
25
+ def self.build_text(x, y, content, family, size)
26
+ TextRenderer.new(content: content, x: x, y: y,
27
+ family: family, size: size).to_svg
28
+ end
29
+ private_class_method :build_text
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Predicate: should this element be rendered, or is it a
8
+ # diagram-frame placeholder that EA skips?
9
+ class Filter
10
+ attr_reader :model_index
11
+
12
+ def initialize(model_index:)
13
+ @model_index = model_index
14
+ end
15
+
16
+ # Returns true if the element should be SKIPPED.
17
+ # EA emits a "diagram frame placeholder" Classifier with
18
+ # no name and no properties on some diagrams — those get
19
+ # dropped. Packages and named Classifiers are always kept.
20
+ def skip?(element)
21
+ return false unless element.background_color == -1
22
+
23
+ classifier = classifier_for(element)
24
+ return false unless classifier
25
+
26
+ classifier.name.to_s.strip.empty? && (classifier.properties.nil? || classifier.properties.empty?)
27
+ end
28
+
29
+ private
30
+
31
+ def classifier_for(element)
32
+ ref = element.model_element_ref
33
+ return nil unless ref
34
+
35
+ candidate = model_index[ref]
36
+ return nil unless candidate.is_a?(Ea::Model::Classifier)
37
+
38
+ candidate
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end