ea 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (544) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +3 -0
  3. data/TODO.complete/01-t-xref-parsing.md +91 -0
  4. data/TODO.complete/02-per-label-geometry.md +50 -0
  5. data/TODO.complete/03-object-properties-constraints.md +57 -0
  6. data/TODO.complete/04-operation-params.md +37 -0
  7. data/TODO.complete/05-pdata-flag-matrix.md +52 -0
  8. data/TODO.complete/06-emf-image-rendering.md +34 -0
  9. data/TODO.complete/07-auxiliary-tables.md +47 -0
  10. data/TODO.complete/10-mdg-stereotype-registry.md +40 -0
  11. data/TODO.complete/11-gml-xsd-generation.md +42 -0
  12. data/TODO.complete/13-shapescript-interpreter.md +28 -0
  13. data/TODO.complete/20-cli-diff.md +46 -0
  14. data/TODO.complete/21-cli-render.md +26 -0
  15. data/TODO.complete/22-cli-export.md +29 -0
  16. data/TODO.complete/23-cli-mdg.md +23 -0
  17. data/TODO.complete/30-headerlines-ocp-refactor.md +72 -0
  18. data/TODO.complete/31-public-send-cleanup.md +61 -0
  19. data/TODO.complete/32-spec-coverage.md +33 -0
  20. data/TODO.complete/33-blocked-parity-gaps.md +41 -0
  21. data/TODO.complete/36-stereotype-icon-wiring.md +32 -0
  22. data/TODO.complete/37-cli-lint.md +32 -0
  23. data/TODO.complete/38-cli-query.md +26 -0
  24. data/TODO.complete/39-cli-info.md +23 -0
  25. data/TODO.complete/40-ocl-evaluator.md +31 -0
  26. data/TODO.complete/41-round-trip-spec.md +18 -0
  27. data/TODO.complete/42-html-diff-report.md +18 -0
  28. data/TODO.complete/43-json-schema-export.md +20 -0
  29. data/TODO.complete/44-remaining-qea-tables.md +40 -0
  30. data/TODO.complete/45-shapescript-extensions.md +25 -0
  31. data/TODO.complete/46-xsd-consolidation.md +20 -0
  32. data/TODO.complete/47-headerlines-legacy-removal.md +20 -0
  33. data/TODO.complete/STATUS.md +109 -0
  34. data/TODO.diagrams/01-svg-measurement-harness.md +31 -0
  35. data/TODO.diagrams/01-xmi-extension-elements-parsing.md +67 -0
  36. data/TODO.diagrams/02-diagram-element-rich-model.md +61 -0
  37. data/TODO.diagrams/02-polygon-over-rendering.md +29 -0
  38. data/TODO.diagrams/03-ea-style-svg-emitter.md +70 -0
  39. data/TODO.diagrams/03-text-over-rendering.md +30 -0
  40. data/TODO.diagrams/04-connector-routing-from-soid-eoid.md +55 -0
  41. data/TODO.diagrams/04-rect-under-rendering.md +33 -0
  42. data/TODO.diagrams/05-canvas-dimension-parity.md +51 -0
  43. data/TODO.diagrams/05-marker-rendering-per-relationship-type.md +49 -0
  44. data/TODO.diagrams/06-connector-labels.md +49 -0
  45. data/TODO.diagrams/06-group-layering-parity.md +39 -0
  46. data/TODO.diagrams/07-canvas-size-and-viewbox.md +39 -0
  47. data/TODO.diagrams/07-text-content-fidelity.md +65 -0
  48. data/TODO.diagrams/08-qea-direct-pipeline.md +46 -0
  49. data/TODO.diagrams/08-visual-regression-harness.md +34 -0
  50. data/TODO.diagrams/09-cli-render-all-diagrams.md +25 -0
  51. data/TODO.diagrams/10-stereotype-color-config.md +29 -0
  52. data/TODO.diagrams/11-elkrb-auto-layout-integration.md +53 -0
  53. data/TODO.diagrams/12-connector-labels.md +40 -0
  54. data/TODO.diagrams/12-per-entity-svg-layer-ordering.md +24 -0
  55. data/TODO.diagrams/13-marker-polygon-vs-path-dispatch.md +20 -0
  56. data/TODO.diagrams/13-per-text-type-font-sizes.md +33 -0
  57. data/TODO.diagrams/14-center-header-text.md +33 -0
  58. data/TODO.diagrams/14-filter-diagram-frame-elements.md +18 -0
  59. data/TODO.diagrams/15-deep-dead-code-audit.md +47 -0
  60. data/TODO.diagrams/15-per-element-font-from-style.md +24 -0
  61. data/TODO.diagrams/16-canvas-marker-extent.md +30 -0
  62. data/TODO.diagrams/16-document-remaining-parity-gaps.md +91 -0
  63. data/TODO.diagrams/17-italic-abstract-classes.md +28 -0
  64. data/TODO.diagrams/17-remaining-parity-gaps.md +90 -0
  65. data/TODO.diagrams/18-canvas-from-image-bounds.md +20 -0
  66. data/TODO.diagrams/18-property-lookup-index.md +42 -0
  67. data/TODO.diagrams/19-connector-sx-sy-routing.md +27 -0
  68. data/TODO.diagrams/19-replace-respond-to-with-is-a.md +31 -0
  69. data/TODO.diagrams/20-split-labels-emitter.md +56 -0
  70. data/TODO.diagrams/20-visibility-glyph-font-encoding.md +27 -0
  71. data/TODO.diagrams/21-aggregation-navigability-arrow.md +21 -0
  72. data/TODO.diagrams/21-auto-generated-legend-block.md +65 -0
  73. data/TODO.diagrams/22-ghost-element-discriminator.md +57 -0
  74. data/TODO.diagrams/22-style-based-path-grouping.md +28 -0
  75. data/TODO.diagrams/23-marker-polygon-deduplication.md +47 -0
  76. data/TODO.diagrams/23-multiplicity-label-positioning.md +29 -0
  77. data/TODO.diagrams/24-marker-anchor-structural.md +35 -0
  78. data/TODO.diagrams/24-track-marker-anchors-structurally.md +66 -0
  79. data/TODO.diagrams/24-truncate-properties-to-fit-box.md +24 -0
  80. data/TODO.diagrams/26-phantom-general-parent-rendering.md +59 -0
  81. data/TODO.diagrams/27-path-grouping-mode-detection.md +43 -0
  82. data/TODO.diagrams/28-architecture-cleanup-batch.md +47 -0
  83. data/TODO.diagrams/30-decompose-elements-groups-for.md +52 -0
  84. data/TODO.diagrams/32-examples-coverage-batch.md +78 -0
  85. data/TODO.diagrams/36-instance-specification-model.md +42 -0
  86. data/TODO.diagrams/37-element-icon-decorations.md +41 -0
  87. data/TODO.diagrams/38-html-note-and-receptions.md +32 -0
  88. data/TODO.diagrams/39-property-stereotype-discriminator.md +40 -0
  89. data/TODO.diagrams/40-standalone-1-discriminator.md +38 -0
  90. data/TODO.diagrams/41-xmi-to-svg-pipeline.md +113 -0
  91. data/TODO.diagrams/42-close-all-parity-deltas.md +69 -0
  92. data/TODO.diagrams/43-marker-deduplication-strategy.md +54 -0
  93. data/TODO.diagrams/44-element-icon-decorations.md +34 -0
  94. data/TODO.diagrams/45-phantom-parent-class-boxes.md +40 -0
  95. data/TODO.diagrams/46-user-drawn-legend-swatches.md +44 -0
  96. data/TODO.diagrams/47-attribute-line-fidelity.md +72 -0
  97. data/TODO.diagrams/48-spec-coverage-critical-emitter-components.md +45 -0
  98. data/TODO.diagrams/49-spec-coverage-orchestrators.md +25 -0
  99. data/TODO.diagrams/50-examples-parity.md +85 -0
  100. data/TODO.diagrams/51-property-oy-gap-and-remaining-gaps.md +48 -0
  101. data/TODO.diagrams/52-marker-registration-architecture.md +89 -0
  102. data/TODO.diagrams/53-remaining-example-gaps.md +86 -0
  103. data/TODO.diagrams/54-display-name-nested-classes.md +40 -0
  104. data/TODO.diagrams/55-visibility-icon-discriminator.md +58 -0
  105. data/TODO.diagrams/56-package-contents-rendering.md +148 -0
  106. data/TODO.diagrams/57-instance-specification-model.md +100 -0
  107. data/TODO.diagrams/58-domain-model-note-text.md +67 -0
  108. data/TODO.diagrams/59-classes-arrow-discriminator.md +57 -0
  109. data/TODO.diagrams/60-testschema-association-marker.md +40 -0
  110. data/TODO.diagrams/61-legend-block-rendering.md +55 -0
  111. data/TODO.diagrams/61-plateau-parity-gaps.md +59 -0
  112. data/TODO.diagrams/62-ghost-element-rendering.md +56 -0
  113. data/TODO.diagrams/63-qualified-name-wrap.md +36 -0
  114. data/TODO.diagrams/64-property-stereotype-labels.md +22 -0
  115. data/TODO.diagrams/65-path-coordinate-frame.md +62 -0
  116. data/TODO.diagrams/67-path-coordinate-frame.md +62 -0
  117. data/TODO.diagrams/68-inherited-attributes.md +65 -0
  118. data/TODO.diagrams/69-package-anchor-conditional.md +29 -0
  119. data/TODO.diagrams/70-mdg-technology-support.md +68 -0
  120. data/TODO.diagrams/71-off-canvas-parent-ghost.md +45 -0
  121. data/TODO.diagrams/72-basic-attr-divider-paths.md +39 -0
  122. data/TODO.diagrams/73-plateau-polygon-gap.md +69 -0
  123. data/TODO.diagrams/74-plateau-rect-gap.md +22 -0
  124. data/TODO.diagrams/75-plateau-text-gap.md +39 -0
  125. data/TODO.diagrams/76-basic-poly-path-gaps.md +41 -0
  126. data/TODO.diagrams/77-basic-text-overrender.md +40 -0
  127. data/TODO.diagrams/78-headerlines-ocp-refactor.md +65 -0
  128. data/TODO.diagrams/79-architecture-backlog.md +65 -0
  129. data/TODO.diagrams/80-basic-triangle-polygons.md +51 -0
  130. data/TODO.diagrams/81-plateau-path-overrender.md +53 -0
  131. data/TODO.diagrams/82-appearance-canvas-offset.md +54 -0
  132. data/TODO.diagrams/83-3d-diagram-subtitle.md +58 -0
  133. data/TODO.diagrams/84-canvas-offset-mismatches.md +61 -0
  134. data/TODO.diagrams/85-phantom-property-overrender.md +95 -0
  135. data/TODO.diagrams/86-foreign-package-rule.md +62 -0
  136. data/TODO.diagrams/87-internal-tech-encryption.md +80 -0
  137. data/TODO.diagrams/EXAMPLES_STATUS.md +88 -0
  138. data/TODO.diagrams/README.md +69 -0
  139. data/TODO.diagrams/STATUS.md +73 -0
  140. data/TODO.fixup/01-default-grouped-paths.md +34 -0
  141. data/TODO.fixup/02-marker-consolidation-per-connector.md +20 -0
  142. data/TODO.fixup/03-diagram-font-inference.md +28 -0
  143. data/TODO.fixup/04-extract-bounds-calculator.md +25 -0
  144. data/TODO.fixup/05-marker-registry-ocp.md +32 -0
  145. data/TODO.fixup/06-connector-path-bend-routing.md +28 -0
  146. data/TODO.fixup/07-edge-attachment-formula.md +22 -0
  147. data/TODO.fixup/08-visual-diff-harness.md +28 -0
  148. data/TODO.fixup/09-extract-element-compartments.md +34 -0
  149. data/TODO.fixup/10-canvas-width-overshoot-fix.md +20 -0
  150. data/TODO.fixup/11-layer-sequencer-extraction.md +18 -0
  151. data/TODO.fixup/12-path-style-harmony.md +23 -0
  152. data/TODO.fixup/13-datatype-stereotype-fallback.md +28 -0
  153. data/TODO.fixup/14-ea-exe-reverse-engineering-notes.md +56 -0
  154. data/TODO.fixup/15-enumeration-literal-rendering.md +30 -0
  155. data/TODO.fixup/16-operation-compartment-rendering.md +42 -0
  156. data/TODO.fixup/17-path-count-calibration.md +27 -0
  157. data/TODO.fixup/18-polygon-count-calibration.md +32 -0
  158. data/TODO.fixup/19-rect-count-undershoot-investigation.md +40 -0
  159. data/TODO.fixup/20-stereotype-color-from-classifier.md +18 -0
  160. data/TODO.fixup/21-visibility-filter-for-properties.md +24 -0
  161. data/TODO.fixup/22-multiplicity-display-rule-calibration.md +25 -0
  162. data/TODO.fixup/23-diagram-frame-title-element.md +31 -0
  163. data/TODO.fixup/24-note-element-rendering.md +23 -0
  164. data/TODO.fixup/25-ea-exe-disassembly-findings.md +177 -0
  165. data/TODO.fixup/26-svg-renderer-architecture.md +78 -0
  166. data/TODO.fixup/27-svg-element-emitters-disassembly.md +135 -0
  167. data/TODO.fixup/28-geometry-parser-identification.md +57 -0
  168. data/TODO.fixup/29-qea-svg-xmi-triple-comparison.md +185 -0
  169. data/TODO.fixup/30-theme-system-stub.md +46 -0
  170. data/TODO.fixup/31-theme-value-object-registry.md +17 -0
  171. data/TODO.fixup/32-styleex-on-diagram-model.md +22 -0
  172. data/TODO.fixup/33-text-encoding-decimals-rotation.md +29 -0
  173. data/TODO.fixup/34-per-type-fill-via-theme.md +18 -0
  174. data/TODO.fixup/35-stroke-width-per-theme.md +7 -0
  175. data/TODO.fixup/36-qea-rect-coord-normalization.md +24 -0
  176. data/TODO.fixup/37-package-shape-rendering.md +22 -0
  177. data/TODO.fixup/38-note-element-rendering.md +15 -0
  178. data/TODO.fixup/39-styleex-flag-honoring.md +25 -0
  179. data/TODO.fixup/40-connector-dashed-lines.md +15 -0
  180. data/TODO.fixup/41-diagram-frame-showborder-detection.md +23 -0
  181. data/TODO.fixup/42-textrenderer-centralization.md +10 -0
  182. data/TODO.fixup/43-colorresolver-unification.md +24 -0
  183. data/TODO.fixup/44-theme-aware-textrenderer.md +17 -0
  184. data/TODO.fixup/45-theme-aware-fontresolver.md +22 -0
  185. data/TODO.fixup/46-style-parameterized-by-theme.md +21 -0
  186. data/TODO.fixup/47-canvas-frame-inset.md +19 -0
  187. data/TODO.fixup/48-background-rect-format.md +15 -0
  188. data/TODO.fixup/49-all-samples-integration-test.md +17 -0
  189. data/TODO.fixup/50-svg-emitter-architecture-docs.md +33 -0
  190. data/TODO.fixup/51-promote-theme-to-ea-namespace.md +28 -0
  191. data/TODO.fixup/52-theme-definition-integrated-fills.md +26 -0
  192. data/TODO.fixup/53-theme-registry-yaml-loading.md +50 -0
  193. data/TODO.fixup/54-diagram-theme-api.md +50 -0
  194. data/TODO.fixup/55-update-eaemitter-to-ea-theme.md +23 -0
  195. data/TODO.fixup/56-theme-yaml-files.md +65 -0
  196. data/TODO.fixup/57-theme-specs-and-integration.md +27 -0
  197. data/TODO.fixup/58-ea-entry-point-theme-autoload.md +20 -0
  198. data/TODO.fixup/59-thread-theme-through-all-renderers.md +29 -0
  199. data/TODO.fixup/60-cli-theme-option.md +22 -0
  200. data/TODO.fixup/61-theme-validation.md +28 -0
  201. data/TODO.fixup/62-theme-builder-dsl.md +31 -0
  202. data/TODO.fixup/63-theme-integration-rendering-spec.md +24 -0
  203. data/TODO.fixup/64-theme-writeback-styleex.md +21 -0
  204. data/TODO.fixup/65-remove-dead-eaemitter-theme-files.md +30 -0
  205. data/TODO.fixup/66-styleex-writeback.md +22 -0
  206. data/TODO.fixup/67-delete-dead-code.md +28 -0
  207. data/TODO.fixup/68-figure-parity-status.md +82 -0
  208. data/TODO.fixup/69-package-polygon-rendering.md +54 -0
  209. data/TODO.fixup/70-tagged-values-compartment.md +39 -0
  210. data/TODO.fixup/71-auto-detect-theme-from-xmi.md +37 -0
  211. data/TODO.fixup/72-sample-exports-as-regression-specs.md +37 -0
  212. data/TODO.fixup/73-delete-dead-transformations.md +14 -0
  213. data/TODO.fixup/74-delete-dead-diagram-renderers.md +18 -0
  214. data/TODO.fixup/75-visibility-legend-icons.md +23 -0
  215. data/TODO.fixup/76-plateau-text-undershoot.md +27 -0
  216. data/TODO.fixup/77-canvas-frame-margins.md +28 -0
  217. data/TODO.fixup/78-note-element-rendering.md +30 -0
  218. data/TODO.fixup/79-operation-compartment-rendering.md +27 -0
  219. data/TODO.fixup/80-tagged-value-stereotype-visibility.md +37 -0
  220. data/TODO.fixup/81-figure-parity-current-state.md +63 -0
  221. data/config/qea_schema.yml +356 -0
  222. data/config/stereotype_colors.yml +15 -0
  223. data/config/themes/119.yml +98 -0
  224. data/config/themes/default.yml +14 -0
  225. data/examples/exports/20251010_current_plateau_v5/UML_EA.DTD +6856 -0
  226. data/examples/exports/20251010_current_plateau_v5/model.xml +96709 -0
  227. data/examples/exports/ArcGISWorkspace_template/UML_EA.DTD +6856 -0
  228. data/examples/exports/ArcGISWorkspace_template/model.xml +1427 -0
  229. data/examples/exports/UmlModel_template/UML_EA.DTD +6856 -0
  230. data/examples/exports/UmlModel_template/model.xml +23 -0
  231. data/examples/exports/basic/UML_EA.DTD +6856 -0
  232. data/examples/exports/basic/model.xml +8039 -0
  233. data/examples/exports/simple/UML_EA.DTD +6856 -0
  234. data/examples/exports/simple/model.xml +585 -0
  235. data/examples/exports/simple_example/UML_EA.DTD +6856 -0
  236. data/examples/exports/simple_example/model.xml +23 -0
  237. data/examples/exports/test/UML_EA.DTD +6856 -0
  238. data/examples/exports/test/model.xml +1638 -0
  239. data/examples/qea/basic.qea +0 -0
  240. data/frontend/README.md +51 -0
  241. data/frontend/dist/app.iife.js +24 -0
  242. data/frontend/dist/style.css +1 -0
  243. data/frontend/index.html +26 -0
  244. data/frontend/package-lock.json +2359 -0
  245. data/frontend/package.json +26 -0
  246. data/frontend/src/App.vue +59 -0
  247. data/frontend/src/app.ts +8 -0
  248. data/frontend/src/components/AppHeader.vue +127 -0
  249. data/frontend/src/components/AppSidebar.vue +119 -0
  250. data/frontend/src/components/ClassDetails.vue +247 -0
  251. data/frontend/src/components/DiagramView.vue +79 -0
  252. data/frontend/src/components/PackageDetails.vue +92 -0
  253. data/frontend/src/components/PackageTreeNode.vue +64 -0
  254. data/frontend/src/components/SearchResults.vue +48 -0
  255. data/frontend/src/components/WelcomeView.vue +87 -0
  256. data/frontend/src/shims-vue.d.ts +5 -0
  257. data/frontend/src/stores/dataStore.ts +79 -0
  258. data/frontend/src/stores/uiStore.ts +167 -0
  259. data/frontend/src/styles/main.css +1566 -0
  260. data/frontend/src/types.ts +221 -0
  261. data/frontend/tests/e2e/__screenshots__/new-dark-mode.png +0 -0
  262. data/frontend/tests/e2e/__screenshots__/new-package-details.png +0 -0
  263. data/frontend/tests/e2e/__screenshots__/new-search-modal.png +0 -0
  264. data/frontend/tests/e2e/__screenshots__/new-sidebar-collapsed.png +0 -0
  265. data/frontend/tests/e2e/__screenshots__/new-welcome.png +0 -0
  266. data/frontend/tests/e2e/render.spec.ts +188 -0
  267. data/frontend/tsconfig.json +20 -0
  268. data/frontend/vite.config.ts +26 -0
  269. data/frontend/vitest.config.ts +8 -0
  270. data/lib/ea/cli/app.rb +62 -1
  271. data/lib/ea/cli/command/diagrams.rb +37 -28
  272. data/lib/ea/cli/command/diff.rb +46 -0
  273. data/lib/ea/cli/command/export.rb +80 -0
  274. data/lib/ea/cli/command/info.rb +75 -0
  275. data/lib/ea/cli/command/lint.rb +48 -0
  276. data/lib/ea/cli/command/mdg.rb +98 -0
  277. data/lib/ea/cli/command/query.rb +34 -0
  278. data/lib/ea/cli/command/render.rb +135 -0
  279. data/lib/ea/cli/command/svg.rb +43 -6
  280. data/lib/ea/cli/command.rb +7 -0
  281. data/lib/ea/diagram/display_config.rb +184 -0
  282. data/lib/ea/diagram.rb +4 -41
  283. data/lib/ea/diff/change.rb +30 -0
  284. data/lib/ea/diff/comparator.rb +82 -0
  285. data/lib/ea/diff/html_reporter.rb +91 -0
  286. data/lib/ea/diff.rb +15 -0
  287. data/lib/ea/export/json/generator.rb +42 -0
  288. data/lib/ea/export/json.rb +12 -0
  289. data/lib/ea/export/json_schema/generator.rb +77 -0
  290. data/lib/ea/export/json_schema.rb +12 -0
  291. data/lib/ea/export/plantuml/generator.rb +73 -0
  292. data/lib/ea/export/plantuml.rb +12 -0
  293. data/lib/ea/export/xmi/generator.rb +77 -0
  294. data/lib/ea/export/xmi.rb +11 -0
  295. data/lib/ea/export/xsd/class_mapping.rb +65 -0
  296. data/lib/ea/export/xsd/generator.rb +142 -0
  297. data/lib/ea/export/xsd/namespace_registry.rb +82 -0
  298. data/lib/ea/export/xsd.rb +19 -0
  299. data/lib/ea/export.rb +14 -0
  300. data/lib/ea/image/emf_renderer.rb +42 -0
  301. data/lib/ea/image.rb +12 -0
  302. data/lib/ea/lint/engine.rb +30 -0
  303. data/lib/ea/lint/lint_rule.rb +38 -0
  304. data/lib/ea/lint/offense.rb +18 -0
  305. data/lib/ea/lint/rules/cyclic_generalization.rb +62 -0
  306. data/lib/ea/lint/rules/duplicate_name.rb +28 -0
  307. data/lib/ea/lint/rules/missing_stereotype.rb +35 -0
  308. data/lib/ea/lint/rules/naming_convention.rb +44 -0
  309. data/lib/ea/lint/rules/orphan_element.rb +28 -0
  310. data/lib/ea/lint/rules.rb +14 -0
  311. data/lib/ea/lint.rb +13 -0
  312. data/lib/ea/mdg/document.rb +120 -0
  313. data/lib/ea/mdg/loader.rb +118 -0
  314. data/lib/ea/mdg/registry.rb +98 -0
  315. data/lib/ea/mdg/stereotype_alias_registry.rb +77 -0
  316. data/lib/ea/mdg/xml/applies_to.rb +18 -0
  317. data/lib/ea/mdg/xml/apply.rb +18 -0
  318. data/lib/ea/mdg/xml/content.rb +18 -0
  319. data/lib/ea/mdg/xml/documentation.rb +24 -0
  320. data/lib/ea/mdg/xml/stereotype.rb +28 -0
  321. data/lib/ea/mdg/xml/stereotypes.rb +18 -0
  322. data/lib/ea/mdg/xml/tag.rb +28 -0
  323. data/lib/ea/mdg/xml/tagged_values.rb +18 -0
  324. data/lib/ea/mdg/xml/technology.rb +28 -0
  325. data/lib/ea/mdg/xml/uml_profile.rb +22 -0
  326. data/lib/ea/mdg/xml/uml_profiles.rb +18 -0
  327. data/lib/ea/mdg/xml.rb +24 -0
  328. data/lib/ea/mdg.rb +25 -0
  329. data/lib/ea/model/classifier.rb +4 -0
  330. data/lib/ea/model/constraint.rb +24 -0
  331. data/lib/ea/model/diagram.rb +85 -0
  332. data/lib/ea/model/diagram_connector.rb +53 -1
  333. data/lib/ea/model/diagram_element.rb +44 -0
  334. data/lib/ea/model/document.rb +47 -2
  335. data/lib/ea/model/ghost_label.rb +32 -0
  336. data/lib/ea/model/instance_specification.rb +34 -0
  337. data/lib/ea/model/legend.rb +43 -0
  338. data/lib/ea/model/legend_item.rb +26 -0
  339. data/lib/ea/model/note.rb +28 -0
  340. data/lib/ea/model/operation.rb +2 -0
  341. data/lib/ea/model/property.rb +2 -0
  342. data/lib/ea/model/relationship.rb +2 -0
  343. data/lib/ea/model/signal.rb +14 -0
  344. data/lib/ea/model/slot.rb +19 -0
  345. data/lib/ea/model.rb +8 -0
  346. data/lib/ea/ocl/evaluator.rb +94 -0
  347. data/lib/ea/ocl/nodes.rb +30 -0
  348. data/lib/ea/ocl/parser.rb +133 -0
  349. data/lib/ea/ocl/unsupported_error.rb +7 -0
  350. data/lib/ea/ocl.rb +13 -0
  351. data/lib/ea/qea/factory/document_builder.rb +24 -10
  352. data/lib/ea/qea/factory/generalization_builder.rb +9 -11
  353. data/lib/ea/qea/infrastructure/table_reader.rb +9 -0
  354. data/lib/ea/qea/models/ea_author.rb +35 -0
  355. data/lib/ea/qea/models/ea_glossary.rb +34 -0
  356. data/lib/ea/qea/models/ea_image.rb +53 -0
  357. data/lib/ea/qea/models/ea_implement.rb +32 -0
  358. data/lib/ea/qea/models/ea_list.rb +42 -0
  359. data/lib/ea/qea/models/ea_object_effort.rb +34 -0
  360. data/lib/ea/qea/models/ea_object_problem.rb +37 -0
  361. data/lib/ea/qea/models/ea_object_require.rb +34 -0
  362. data/lib/ea/qea/models/ea_object_resource.rb +34 -0
  363. data/lib/ea/qea/models/ea_object_risk.rb +34 -0
  364. data/lib/ea/qea/models/ea_object_scenario.rb +34 -0
  365. data/lib/ea/qea/models/ea_object_test.rb +35 -0
  366. data/lib/ea/qea/models/ea_object_trx.rb +34 -0
  367. data/lib/ea/qea/models/ea_operation.rb +7 -0
  368. data/lib/ea/qea/models/ea_palette.rb +30 -0
  369. data/lib/ea/qea/models/ea_palette_item.rb +36 -0
  370. data/lib/ea/qea/models/ea_phase.rb +40 -0
  371. data/lib/ea/qea/models/ea_role_constraint.rb +32 -0
  372. data/lib/ea/qea/models/ea_secrypt.rb +34 -0
  373. data/lib/ea/qea/models/ea_version.rb +39 -0
  374. data/lib/ea/qea/models/ea_xref.rb +11 -1
  375. data/lib/ea/qea/models.rb +19 -0
  376. data/lib/ea/qea/services/database_loader.rb +22 -1
  377. data/lib/ea/query/builder.rb +108 -0
  378. data/lib/ea/query.rb +9 -0
  379. data/lib/ea/render/image_converter.rb +101 -0
  380. data/lib/ea/render.rb +10 -0
  381. data/lib/ea/shapescript/parser.rb +208 -0
  382. data/lib/ea/shapescript/renderer.rb +74 -0
  383. data/lib/ea/shapescript/shape.rb +34 -0
  384. data/lib/ea/shapescript.rb +24 -0
  385. data/lib/ea/sources/qea/adapter.rb +22 -5
  386. data/lib/ea/sources/qea/classifier_builder.rb +184 -7
  387. data/lib/ea/sources/qea/diagram_builder.rb +352 -24
  388. data/lib/ea/sources/qea/id_normalizer.rb +16 -1
  389. data/lib/ea/sources/qea/instance_builder.rb +113 -0
  390. data/lib/ea/sources/qea/legend_builder.rb +137 -0
  391. data/lib/ea/sources/qea/note_builder.rb +58 -0
  392. data/lib/ea/sources/qea/object_classifier_map.rb +6 -1
  393. data/lib/ea/sources/qea/operation_builder.rb +1 -0
  394. data/lib/ea/sources/qea/package_builder.rb +16 -0
  395. data/lib/ea/sources/qea/relationship_builder.rb +21 -5
  396. data/lib/ea/sources/qea/tagged_value_builder.rb +28 -9
  397. data/lib/ea/sources/qea/xref/legend_definition.rb +35 -0
  398. data/lib/ea/sources/qea/xref/parser.rb +150 -0
  399. data/lib/ea/sources/qea/xref/property_definition.rb +29 -0
  400. data/lib/ea/sources/qea/xref/record.rb +41 -0
  401. data/lib/ea/sources/qea/xref/stereotype_application.rb +37 -0
  402. data/lib/ea/sources/qea/xref.rb +27 -0
  403. data/lib/ea/sources/qea.rb +4 -0
  404. data/lib/ea/sources/xmi/adapter.rb +80 -2
  405. data/lib/ea/sources/xmi/annotation_builder.rb +1 -1
  406. data/lib/ea/sources/xmi/classifier_builder.rb +10 -1
  407. data/lib/ea/sources/xmi/diagram_builder.rb +242 -45
  408. data/lib/ea/sources/xmi/extension_elements.rb +74 -0
  409. data/lib/ea/sources/xmi/extension_geometry_parser.rb +124 -0
  410. data/lib/ea/sources/xmi/extension_style_parser.rb +91 -0
  411. data/lib/ea/sources/xmi/note_builder.rb +46 -0
  412. data/lib/ea/sources/xmi/property_builder.rb +9 -0
  413. data/lib/ea/sources/xmi/stereotype_builder.rb +54 -0
  414. data/lib/ea/sources/xmi/tag_builder.rb +45 -0
  415. data/lib/ea/sources/xmi/umldi_keyword_extractor.rb +87 -0
  416. data/lib/ea/sources/xmi.rb +7 -0
  417. data/lib/ea/spa/output/sharded_multi_file_strategy.rb +18 -3
  418. data/lib/ea/spa/output/single_file_strategy.rb +8 -2
  419. data/lib/ea/spa/output/strategy.rb +34 -0
  420. data/lib/ea/svg/connector_router.rb +134 -0
  421. data/lib/ea/svg/ea_emitter/background.rb +15 -0
  422. data/lib/ea/svg/ea_emitter/bounds_calculator.rb +142 -0
  423. data/lib/ea/svg/ea_emitter/canvas.rb +88 -0
  424. data/lib/ea/svg/ea_emitter/color_resolver.rb +56 -0
  425. data/lib/ea/svg/ea_emitter/compartment/attributes.rb +28 -0
  426. data/lib/ea/svg/ea_emitter/compartment/constraints.rb +30 -0
  427. data/lib/ea/svg/ea_emitter/compartment/enum_literals.rb +86 -0
  428. data/lib/ea/svg/ea_emitter/compartment/header.rb +32 -0
  429. data/lib/ea/svg/ea_emitter/compartment/header_divider.rb +57 -0
  430. data/lib/ea/svg/ea_emitter/compartment/instance_slots.rb +96 -0
  431. data/lib/ea/svg/ea_emitter/compartment/instance_underline.rb +67 -0
  432. data/lib/ea/svg/ea_emitter/compartment/note_body.rb +52 -0
  433. data/lib/ea/svg/ea_emitter/compartment/note_text.rb +102 -0
  434. data/lib/ea/svg/ea_emitter/compartment/operations.rb +38 -0
  435. data/lib/ea/svg/ea_emitter/compartment/package_contents.rb +72 -0
  436. data/lib/ea/svg/ea_emitter/compartment/package_from_parent.rb +136 -0
  437. data/lib/ea/svg/ea_emitter/compartment/shape.rb +82 -0
  438. data/lib/ea/svg/ea_emitter/compartment/tagged_values.rb +30 -0
  439. data/lib/ea/svg/ea_emitter/compartment.rb +58 -0
  440. data/lib/ea/svg/ea_emitter/connectors.rb +84 -0
  441. data/lib/ea/svg/ea_emitter/diagram_frame.rb +143 -0
  442. data/lib/ea/svg/ea_emitter/document.rb +35 -0
  443. data/lib/ea/svg/ea_emitter/element/attribute_line_builder.rb +117 -0
  444. data/lib/ea/svg/ea_emitter/element/attribute_renderer.rb +119 -0
  445. data/lib/ea/svg/ea_emitter/element/bcol_decoder.rb +29 -0
  446. data/lib/ea/svg/ea_emitter/element/compartment_geometry.rb +113 -0
  447. data/lib/ea/svg/ea_emitter/element/constraint_renderer.rb +53 -0
  448. data/lib/ea/svg/ea_emitter/element/divider_renderer.rb +17 -0
  449. data/lib/ea/svg/ea_emitter/element/enumeration_literal_renderer.rb +34 -0
  450. data/lib/ea/svg/ea_emitter/element/filter.rb +44 -0
  451. data/lib/ea/svg/ea_emitter/element/hand_draw_shape_renderer.rb +77 -0
  452. data/lib/ea/svg/ea_emitter/element/header_line_pipeline.rb +43 -0
  453. data/lib/ea/svg/ea_emitter/element/header_line_provider/instance_spec.rb +27 -0
  454. data/lib/ea/svg/ea_emitter/element/header_line_provider/name.rb +76 -0
  455. data/lib/ea/svg/ea_emitter/element/header_line_provider/parent_ghost.rb +25 -0
  456. data/lib/ea/svg/ea_emitter/element/header_line_provider/stereotype_label.rb +68 -0
  457. data/lib/ea/svg/ea_emitter/element/header_line_provider.rb +19 -0
  458. data/lib/ea/svg/ea_emitter/element/header_lines.rb +27 -0
  459. data/lib/ea/svg/ea_emitter/element/header_renderer.rb +48 -0
  460. data/lib/ea/svg/ea_emitter/element/icon_renderer.rb +109 -0
  461. data/lib/ea/svg/ea_emitter/element/legend_renderer.rb +177 -0
  462. data/lib/ea/svg/ea_emitter/element/note_shape_renderer.rb +112 -0
  463. data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +100 -0
  464. data/lib/ea/svg/ea_emitter/element/package_shape_renderer.rb +181 -0
  465. data/lib/ea/svg/ea_emitter/element/row_icon_renderer.rb +156 -0
  466. data/lib/ea/svg/ea_emitter/element/shape_renderer.rb +17 -0
  467. data/lib/ea/svg/ea_emitter/element/stereotype_icon_renderer.rb +82 -0
  468. data/lib/ea/svg/ea_emitter/element/tagged_value_renderer.rb +50 -0
  469. data/lib/ea/svg/ea_emitter/element/text_escape.rb +20 -0
  470. data/lib/ea/svg/ea_emitter/element.rb +37 -0
  471. data/lib/ea/svg/ea_emitter/elements.rb +357 -0
  472. data/lib/ea/svg/ea_emitter/font_resolver.rb +111 -0
  473. data/lib/ea/svg/ea_emitter/ghost_labels.rb +66 -0
  474. data/lib/ea/svg/ea_emitter/label/end_label.rb +266 -0
  475. data/lib/ea/svg/ea_emitter/label/midpoint_label.rb +101 -0
  476. data/lib/ea/svg/ea_emitter/label/registry.rb +50 -0
  477. data/lib/ea/svg/ea_emitter/label.rb +23 -0
  478. data/lib/ea/svg/ea_emitter/labels.rb +130 -0
  479. data/lib/ea/svg/ea_emitter/layer.rb +20 -0
  480. data/lib/ea/svg/ea_emitter/layer_sequencer.rb +93 -0
  481. data/lib/ea/svg/ea_emitter/marker/arrow_path.rb +52 -0
  482. data/lib/ea/svg/ea_emitter/marker/diamond.rb +57 -0
  483. data/lib/ea/svg/ea_emitter/marker/kind.rb +35 -0
  484. data/lib/ea/svg/ea_emitter/marker/open_triangle.rb +27 -0
  485. data/lib/ea/svg/ea_emitter/marker/package_import.rb +39 -0
  486. data/lib/ea/svg/ea_emitter/marker/plus.rb +40 -0
  487. data/lib/ea/svg/ea_emitter/marker/registry.rb +42 -0
  488. data/lib/ea/svg/ea_emitter/marker.rb +37 -0
  489. data/lib/ea/svg/ea_emitter/markers.rb +404 -0
  490. data/lib/ea/svg/ea_emitter/render_context.rb +52 -0
  491. data/lib/ea/svg/ea_emitter/style.rb +15 -0
  492. data/lib/ea/svg/ea_emitter/text_renderer.rb +92 -0
  493. data/lib/ea/svg/ea_emitter.rb +51 -0
  494. data/lib/ea/svg/parity/checker.rb +105 -0
  495. data/lib/ea/svg/parity/source.rb +59 -0
  496. data/lib/ea/svg/parity/suite.rb +113 -0
  497. data/lib/ea/svg/parity.rb +14 -0
  498. data/lib/ea/svg/stereotype_color_resolver.rb +50 -0
  499. data/lib/ea/svg.rb +4 -15
  500. data/lib/ea/theme/definition.rb +176 -0
  501. data/lib/ea/theme/loader.rb +125 -0
  502. data/lib/ea/theme/registry.rb +96 -0
  503. data/lib/ea/theme.rb +37 -0
  504. data/lib/ea/transformations.rb +5 -88
  505. data/lib/ea/version.rb +4 -1
  506. data/lib/ea.rb +14 -4
  507. data/lib/tasks/svg_bench.rake +87 -0
  508. data/lutaml-uml-0.5.1.gem +0 -0
  509. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes-list.png +0 -0
  510. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes.png +0 -0
  511. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-2-gradients.png +0 -0
  512. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-3-colors.png +0 -0
  513. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-4-appearance-fonts.png +0 -0
  514. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-4-appearance.png +0 -0
  515. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-5-behavior.png +0 -0
  516. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-6-sequence.png +0 -0
  517. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram.png +0 -0
  518. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-2-objects.png +0 -0
  519. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-3-links.png +0 -0
  520. metadata +473 -26
  521. data/lib/ea/diagram/configuration.rb +0 -379
  522. data/lib/ea/diagram/element_renderers/base_renderer.rb +0 -77
  523. data/lib/ea/diagram/element_renderers/class_renderer.rb +0 -323
  524. data/lib/ea/diagram/element_renderers/connector_renderer.rb +0 -41
  525. data/lib/ea/diagram/element_renderers/package_renderer.rb +0 -61
  526. data/lib/ea/diagram/element_renderers.rb +0 -43
  527. data/lib/ea/diagram/extractor.rb +0 -566
  528. data/lib/ea/diagram/layout_engine.rb +0 -170
  529. data/lib/ea/diagram/path_builder.rb +0 -202
  530. data/lib/ea/diagram/style_parser.rb +0 -42
  531. data/lib/ea/diagram/style_resolver.rb +0 -291
  532. data/lib/ea/diagram/svg_renderer.rb +0 -274
  533. data/lib/ea/diagram/util.rb +0 -73
  534. data/lib/ea/svg/bounds_calculator.rb +0 -74
  535. data/lib/ea/svg/connector_path.rb +0 -138
  536. data/lib/ea/svg/element_box.rb +0 -256
  537. data/lib/ea/svg/renderer.rb +0 -77
  538. data/lib/ea/svg/style_resolver.rb +0 -72
  539. data/lib/ea/transformations/configuration.rb +0 -333
  540. data/lib/ea/transformations/format_registry.rb +0 -366
  541. data/lib/ea/transformations/parsers/base_parser.rb +0 -482
  542. data/lib/ea/transformations/parsers/qea_parser.rb +0 -401
  543. data/lib/ea/transformations/parsers/xmi_parser.rb +0 -243
  544. data/lib/ea/transformations/transformation_engine.rb +0 -390
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Sources
5
+ module Xmi
6
+ # Extracts per-element stereotype applications from EA's
7
+ # `<xmi:Extension>/<elements>` block. EA stores stereotype
8
+ # info in `<element xmi:idref="EAID_...">/<properties
9
+ # stereotype="X"/>`. The uml:Model packaged elements carry
10
+ # type info but not stereotypes.
11
+ #
12
+ # Returns a Hash{element_id => Array<String>} of stereotype
13
+ # names applied to that element.
14
+ class StereotypeBuilder
15
+ attr_reader :root
16
+
17
+ def initialize(root)
18
+ @root = root
19
+ end
20
+
21
+ def grouped_by_element
22
+ @grouped ||= begin
23
+ elements = root.extension&.elements
24
+ return {} unless elements
25
+
26
+ elements.element.each_with_object({}) do |ext_element, acc|
27
+ id = ext_element.idref
28
+ next unless id
29
+
30
+ stereotypes = stereotypes_for(ext_element)
31
+ next if stereotypes.empty?
32
+
33
+ acc[id] = stereotypes
34
+ end
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # The `<properties stereotype="X"/>` attribute holds the
41
+ # primary stereotype. Additional stereotypes from `<xrefs>`
42
+ # are not captured here — that would require parsing the
43
+ # packed XREFPROP string format.
44
+ def stereotypes_for(ext_element)
45
+ props = ext_element.properties
46
+ value = props&.stereotype
47
+ return [] if value.nil? || value.to_s.empty?
48
+
49
+ [value.to_s]
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ module Ea
6
+ module Sources
7
+ module Xmi
8
+ # Extracts tagged values from the `<xmi:Extension><tags><tag/>`
9
+ # block. The xmi gem does not expose Sparx tags directly, so we
10
+ # parse the raw XML once and group tags by their
11
+ # `modelElement` attribute. Each tag's value is truncated at
12
+ # the first `#` (EA encodes notes after that marker).
13
+ class TagBuilder
14
+ attr_reader :xmi_path
15
+
16
+ def initialize(xmi_path)
17
+ @xmi_path = xmi_path
18
+ end
19
+
20
+ # Returns Hash{model_element_id => Array<Ea::Model::TaggedValue>}
21
+ def grouped_by_model_element
22
+ return {} if xmi_path.nil?
23
+
24
+ @grouped ||= begin
25
+ doc = Nokogiri::XML(File.read(xmi_path))
26
+ # <tags><tag/></tags> lives inside per-element <element>
27
+ # blocks within <xmi:Extension>. The element itself is in
28
+ # no XML namespace; match by local-name to be safe.
29
+ doc.xpath(%(//*[local-name()="tags"]/*[local-name()="tag"]))
30
+ .each_with_object({}) do |node, acc|
31
+ me = node["modelElement"]
32
+ next unless me
33
+
34
+ key = node["name"].to_s
35
+ value = node["value"].to_s.split("#", 2).first
36
+ next if key.empty?
37
+
38
+ (acc[me] ||= []) << Ea::Model::TaggedValue.new(key: key, value: value)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ module Ea
6
+ module Sources
7
+ module Xmi
8
+ # Reads UMLDI `<ownedElement>` blocks from the XMI source and
9
+ # returns the keyword text EA emitted for each modeled element.
10
+ #
11
+ # The xmi gem's typed parser does not surface umldi: keyword
12
+ # labels, but EA encodes them as
13
+ # `<ownedElement xmi:type="umldi:UMLKeywordLabel" text="Type"/>`.
14
+ # This class pre-parses the raw XML once and indexes by the
15
+ # referenced classifier ID.
16
+ #
17
+ # Returned text is the raw keyword ("Type", "FeatureType",
18
+ # "dataType"). The emitter wraps it in «...» when rendering.
19
+ class UmldiKeywordExtractor
20
+ # Sparx EA 6.5 declares umldi: as the 20161101 namespace on
21
+ # the XMI root. Older EAs use the 20131001 version. Try both.
22
+ UMLDI_NAMESPACES = [
23
+ "http://www.omg.org/spec/UML/20161101/UMLDI",
24
+ "http://www.omg.org/spec/UML/20131001/UMLDI"
25
+ ].freeze
26
+
27
+ attr_reader :xmi_path
28
+
29
+ def initialize(xmi_path)
30
+ @xmi_path = xmi_path
31
+ end
32
+
33
+ # Returns { classifier_id => keyword_text } for every
34
+ # umldi:UMLKeywordLabel found, scoped to the given diagram.
35
+ def keywords_for_diagram(diagram_id)
36
+ return {} unless File.exist?(xmi_path)
37
+
38
+ keywords_by_diagram[diagram_id] || {}
39
+ end
40
+
41
+ private
42
+
43
+ def doc
44
+ @doc ||= Nokogiri::XML(File.read(xmi_path))
45
+ end
46
+
47
+ def keywords_by_diagram
48
+ @keywords_by_diagram ||= begin
49
+ result = {}
50
+ # The Sparx XMI export declares the umldi: namespace
51
+ # but the elements inside Diagram have lost their
52
+ # prefix binding once parsed by Nokogiri — the
53
+ # element name resolves to local-name "ownedElement"
54
+ # and the umldi:UMLClassifierShape type information
55
+ # lives in the literal `type` attribute. We therefore
56
+ # match shape children by the presence of `modelElement`
57
+ # and the keyword label by its `text` attribute.
58
+ doc.xpath("//*[local-name()=\"Diagram\"]").each do |diagram|
59
+ diagram_id = diagram["id"] || diagram["xmi:id"]
60
+ next unless diagram_id
61
+
62
+ keywords = {}
63
+ diagram.xpath("./ownedElement[@modelElement]").each do |shape|
64
+ model_id = shape["modelElement"]
65
+ next unless model_id
66
+
67
+ # Keyword label is the first ownedElement child
68
+ # with a non-empty `text` attribute. The name
69
+ # label follows it.
70
+ shape.xpath("./ownedElement[@text]").each do |child|
71
+ text = child["text"]
72
+ next if text.nil? || text.empty?
73
+ next if text.start_with?("«") # safety
74
+
75
+ keywords[model_id] = text
76
+ break
77
+ end
78
+ end
79
+ result[diagram_id] = keywords unless keywords.empty?
80
+ end
81
+ result
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -22,7 +22,14 @@ module Ea
22
22
  autoload :OperationBuilder, "ea/sources/xmi/operation_builder"
23
23
  autoload :RelationshipBuilder, "ea/sources/xmi/relationship_builder"
24
24
  autoload :AnnotationBuilder, "ea/sources/xmi/annotation_builder"
25
+ autoload :TagBuilder, "ea/sources/xmi/tag_builder"
26
+ autoload :NoteBuilder, "ea/sources/xmi/note_builder"
27
+ autoload :StereotypeBuilder, "ea/sources/xmi/stereotype_builder"
25
28
  autoload :DiagramBuilder, "ea/sources/xmi/diagram_builder"
29
+ autoload :ExtensionElements, "ea/sources/xmi/extension_elements"
30
+ autoload :ExtensionGeometryParser, "ea/sources/xmi/extension_geometry_parser"
31
+ autoload :ExtensionStyleParser, "ea/sources/xmi/extension_style_parser"
32
+ autoload :UmldiKeywordExtractor, "ea/sources/xmi/umldi_keyword_extractor"
26
33
  autoload :Adapter, "ea/sources/xmi/adapter"
27
34
  end
28
35
  end
@@ -6,11 +6,14 @@ module Ea
6
6
  module Spa
7
7
  module Output
8
8
  # Sharded layout: directory with skeleton.json, search.json,
9
- # and one JSON file per entity under data/. The frontend
10
- # loads skeleton + search upfront and fetches shards on
11
- # demand. This is the layout for large models.
9
+ # one JSON file per entity under data/, the pre-built SPA
10
+ # bundle (app.js + style.css), and an index.html shell that
11
+ # ties them together. The frontend loads skeleton + search
12
+ # upfront and fetches shards on demand. Suitable for large
13
+ # models that won't fit comfortably in a single HTML file.
12
14
  class ShardedMultiFileStrategy < Strategy
13
15
  def render(projector)
16
+ assert_frontend_bundle!
14
17
  FileUtils.mkdir_p(output_path)
15
18
  FileUtils.mkdir_p(File.join(output_path, "data"))
16
19
 
@@ -19,6 +22,7 @@ module Ea
19
22
  write_json(File.join(output_path, "search.json"),
20
23
  projector.search_index)
21
24
  write_shards(projector)
25
+ write_assets
22
26
  write_index_html(skeleton)
23
27
 
24
28
  output_path
@@ -42,6 +46,15 @@ module Ea
42
46
  end
43
47
  end
44
48
 
49
+ # Copy the pre-built JS + CSS bundles next to index.html so
50
+ # the shell can <script src="app.js"> them. Assets are always
51
+ # the same bytes for every generation, so a plain file copy
52
+ # is both simplest and fastest.
53
+ def write_assets
54
+ FileUtils.cp(FRONTEND_APP_JS, File.join(output_path, "app.js"))
55
+ FileUtils.cp(FRONTEND_STYLE_CSS, File.join(output_path, "style.css"))
56
+ end
57
+
45
58
  def write_index_html(skeleton)
46
59
  title = skeleton.view_extras&.dig("ui", "title") ||
47
60
  skeleton.metadata&.fetch("title", nil) ||
@@ -53,6 +66,7 @@ module Ea
53
66
  <meta charset="UTF-8">
54
67
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
55
68
  <title>#{title}</title>
69
+ <link rel="stylesheet" href="style.css">
56
70
  </head>
57
71
  <body>
58
72
  <div id="app"></div>
@@ -61,6 +75,7 @@ module Ea
61
75
  window.__SPA_SEARCH_URL__ = 'search.json';
62
76
  window.__SPA_SHARD_BASE__ = 'data/';
63
77
  </script>
78
+ <script src="app.js" defer></script>
64
79
  </body>
65
80
  </html>
66
81
  HTML
@@ -1,16 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fileutils"
4
+ require "json"
4
5
 
5
6
  module Ea
6
7
  module Spa
7
8
  module Output
8
- # Embeds skeleton + search + every shard in one HTML file
9
- # (data inlined as a JSON blob). Suitable for small models
9
+ # Embeds skeleton + search + every shard AND the pre-built SPA
10
+ # bundle (JS + CSS) in one HTML file. Suitable for small models
10
11
  # only; not appropriate when shard count or total payload is
11
12
  # large.
12
13
  class SingleFileStrategy < Strategy
13
14
  def render(projector)
15
+ assert_frontend_bundle!
14
16
  FileUtils.mkdir_p(File.dirname(output_path))
15
17
 
16
18
  skeleton = projector.skeleton
@@ -33,6 +35,8 @@ module Ea
33
35
  title = payload.dig(:viewExtras, "ui", "title") ||
34
36
  payload[:metadata]&.fetch("title", nil) ||
35
37
  "Ea::Spa"
38
+ css = frontend_style_css || ""
39
+ js = frontend_app_js || ""
36
40
  <<~HTML
37
41
  <!DOCTYPE html>
38
42
  <html lang="en">
@@ -40,12 +44,14 @@ module Ea
40
44
  <meta charset="UTF-8">
41
45
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
42
46
  <title>#{title}</title>
47
+ <style>#{css}</style>
43
48
  </head>
44
49
  <body>
45
50
  <div id="app"></div>
46
51
  <script>
47
52
  window.__SPA_DATA__ = #{payload.to_json};
48
53
  </script>
54
+ <script>#{js}</script>
49
55
  </body>
50
56
  </html>
51
57
  HTML
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
4
+
3
5
  module Ea
4
6
  module Spa
5
7
  module Output
@@ -13,6 +15,14 @@ module Ea
13
15
  class Strategy
14
16
  SIZE_THRESHOLD_BYTES = 2 * 1024 * 1024 # 2 MB
15
17
 
18
+ # Path to the pre-built Vue IIFE bundle shipped with the gem.
19
+ # Built from frontend/ via `npm run build`; committed so the
20
+ # gem never needs npm at install time. Resolved lazily so a
21
+ # missing bundle raises a clear error pointing at frontend/.
22
+ FRONTEND_DIST_DIR = File.expand_path("../../../../frontend/dist", __dir__)
23
+ FRONTEND_APP_JS = File.join(FRONTEND_DIST_DIR, "app.iife.js")
24
+ FRONTEND_STYLE_CSS = File.join(FRONTEND_DIST_DIR, "style.css")
25
+
16
26
  attr_reader :output_path
17
27
 
18
28
  def initialize(output_path)
@@ -24,6 +34,30 @@ module Ea
24
34
  "#{self.class} must implement #render"
25
35
  end
26
36
 
37
+ protected
38
+
39
+ # The pre-built SPA application source as a String, for
40
+ # embedding directly into a single-file output. Returns nil
41
+ # if the bundle has not been built (e.g. running from a
42
+ # source checkout before `npm run build`).
43
+ def frontend_app_js
44
+ return nil unless File.exist?(FRONTEND_APP_JS)
45
+ File.read(FRONTEND_APP_JS)
46
+ end
47
+
48
+ def frontend_style_css
49
+ return nil unless File.exist?(FRONTEND_STYLE_CSS)
50
+ File.read(FRONTEND_STYLE_CSS)
51
+ end
52
+
53
+ def assert_frontend_bundle!
54
+ unless File.exist?(FRONTEND_APP_JS) && File.exist?(FRONTEND_STYLE_CSS)
55
+ raise Ea::Error,
56
+ "SPA frontend bundle not found under #{FRONTEND_DIST_DIR}. " \
57
+ "Run `cd frontend && npm install && npm run build`."
58
+ end
59
+ end
60
+
27
61
  private
28
62
 
29
63
  def write_json(path, data)
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ class ConnectorRouter
6
+ # Empirically determined offset EA adds to top-edge
7
+ # attachment points: connector starts ~9 px below the
8
+ # element's logical top edge (matches header height).
9
+ EDGE_TOP_OFFSET = 9
10
+
11
+ attr_reader :source_bounds, :target_bounds, :edge_code,
12
+ :source_delta, :target_delta, :bend_path
13
+
14
+ def initialize(source_bounds:, target_bounds:,
15
+ edge_code: nil,
16
+ source_delta: [0, 0], target_delta: [0, 0],
17
+ bend_path: nil)
18
+ @source_bounds = source_bounds
19
+ @target_bounds = target_bounds
20
+ @edge_code = edge_code
21
+ @source_delta = source_delta
22
+ @target_delta = target_delta
23
+ @bend_path = bend_path || []
24
+ end
25
+
26
+ def waypoints
27
+ src_pt = source_point
28
+ tgt_pt = target_point
29
+ return [] unless src_pt && tgt_pt
30
+
31
+ # Path= values from EA are ABSOLUTE pixel positions on the
32
+ # diagram canvas (verified against reference SVGs). Use them
33
+ # as-is — do not offset by src_pt.
34
+ return [src_pt, *bend_path, tgt_pt] if bend_path.any?
35
+
36
+ points = [src_pt]
37
+ bend = bend_point(src_pt, tgt_pt)
38
+ points << bend if bend
39
+ points << tgt_pt
40
+ points
41
+ end
42
+
43
+ private
44
+
45
+ def source_point
46
+ return nil unless source_bounds
47
+ pt = if @edge_code && @edge_code != 0
48
+ edge_point(source_bounds, @edge_code)
49
+ else
50
+ primary_edge(source_bounds, target_bounds)
51
+ end
52
+ return nil unless pt
53
+
54
+ offset_by_delta(pt, source_delta)
55
+ end
56
+
57
+ def target_point
58
+ return nil unless target_bounds
59
+ pt = if @edge_code && @edge_code != 0
60
+ opposite_edge_point(source_bounds, target_bounds, @edge_code)
61
+ else
62
+ primary_edge(target_bounds, source_bounds)
63
+ end
64
+ return nil unless pt
65
+
66
+ offset_by_delta(pt, target_delta)
67
+ end
68
+
69
+ def offset_by_delta(point, delta)
70
+ return point if delta.nil?
71
+
72
+ [point[0] + (delta[0] || 0), point[1] + (delta[1] || 0)]
73
+ end
74
+
75
+ def edge_point(bounds, code)
76
+ case code
77
+ when 1 then [center_x(bounds), bounds.y + EDGE_TOP_OFFSET]
78
+ when 2 then [bounds.x + bounds.width, center_y(bounds)]
79
+ when 3 then [center_x(bounds), bounds.y + bounds.height]
80
+ when 4 then [bounds.x, center_y(bounds)]
81
+ else [center_x(bounds), center_y(bounds)]
82
+ end
83
+ end
84
+
85
+ def opposite_edge_point(source, target, code)
86
+ case code
87
+ when 1 then [center_x(target), target.y + target.height]
88
+ when 2 then [target.x, center_y(target)]
89
+ when 3 then [center_x(target), target.y]
90
+ when 4 then [target.x + target.width, center_y(target)]
91
+ else [center_x(target), center_y(target)]
92
+ end
93
+ end
94
+
95
+ def primary_edge(bounds, other)
96
+ return nil unless bounds && other
97
+
98
+ dx = center_x(bounds) - center_x(other)
99
+ dy = center_y(bounds) - center_y(other)
100
+
101
+ if dx.abs > dy.abs
102
+ dx.positive? ? [bounds.x, center_y(bounds)] : [bounds.x + bounds.width, center_y(bounds)]
103
+ else
104
+ dy.positive? ? [center_x(bounds), bounds.y] : [center_x(bounds), bounds.y + bounds.height]
105
+ end
106
+ end
107
+
108
+ def bend_point(src, tgt)
109
+ return nil if src == tgt
110
+ return nil if src[0] == tgt[0] || src[1] == tgt[1]
111
+
112
+ if horizontal_exit?
113
+ [tgt[0], src[1]]
114
+ else
115
+ [src[0], tgt[1]]
116
+ end
117
+ end
118
+
119
+ def horizontal_exit?
120
+ return false unless source_bounds && @edge_code
121
+
122
+ @edge_code == 2 || @edge_code == 4
123
+ end
124
+
125
+ def center_x(bounds)
126
+ bounds.x + (bounds.width / 2.0)
127
+ end
128
+
129
+ def center_y(bounds)
130
+ bounds.y + (bounds.height / 2.0)
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_string: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Background
7
+ module_function
8
+
9
+ def render(canvas)
10
+ %(<g style="fill:#FFFFFF;fill-opacity:1.00;">\n <rect x="0" y="0" width="#{canvas.width}" height="#{canvas.height}" shape-rendering="auto"/>\n</g>)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ # Computes the (min_x, min_y, width, height) tuple for a
7
+ # Diagram's canvas. Encapsulates the union rules so Canvas
8
+ # stays a small value-object with just coordinate translation
9
+ # and formatting concerns.
10
+ #
11
+ # Sources contributing to the bounds:
12
+ # - ElementBounds: logical x-extent, logical+image y-extent
13
+ # - ConnectorBounds: waypoint positions
14
+ # - MarkerExtent: MARKER_EXTENT padding around end points
15
+ # - PackageTabExtent: extra space above Package elements for
16
+ # the tab polygon (which sits above the
17
+ # element's logical top).
18
+ #
19
+ # Each source is a method returning an Array<[x, y]> points.
20
+ # Composing new sources = adding a method, no modification of
21
+ # existing ones (OCP).
22
+ class BoundsCalculator
23
+ MARKER_EXTENT = 15
24
+ PACKAGE_TAB_HEIGHT = 20
25
+ # EA's canvas includes non-uniform frame insets around the
26
+ # element content. Reverse-engineered from reference SVG
27
+ # byte-diff: maintenance diagram has element 169x80 at
28
+ # logical (0,0), ref canvas is 254x177 with element at
29
+ # (35, 40). So:
30
+ # canvas_width = element_width + INSET_LEFT + INSET_RIGHT
31
+ # canvas_height = element_height + INSET_TOP + INSET_BOTTOM
32
+ # The top inset accommodates the frame tab + label space
33
+ # above the element.
34
+ INSET_LEFT = 35
35
+ INSET_RIGHT = 50
36
+ INSET_TOP = 40
37
+ INSET_BOTTOM = 57
38
+
39
+ attr_reader :diagram, :model_index
40
+
41
+ def initialize(diagram, model_index: nil)
42
+ @diagram = diagram
43
+ @model_index = model_index
44
+ end
45
+
46
+ def compute
47
+ points = []
48
+ points.concat(element_points)
49
+ points.concat(connector_points)
50
+ points.concat(marker_points)
51
+ points.concat(package_tab_points)
52
+ return [0, 0, 1, 1] if points.empty?
53
+
54
+ xs = points.map(&:first)
55
+ ys = points.map(&:last)
56
+ min_x = xs.min || 0
57
+ min_y = ys.min || 0
58
+ [
59
+ min_x,
60
+ min_y,
61
+ (xs.max - min_x) + INSET_LEFT + INSET_RIGHT,
62
+ (ys.max - min_y) + INSET_TOP + INSET_BOTTOM
63
+ ]
64
+ end
65
+
66
+ private
67
+
68
+ # Element x-extent: logical bounds only (matches EA's canvas
69
+ # left/right exactly).
70
+ # Element y-extent: union of logical + image_bounds (image
71
+ # extends below bounds for shadow / image padding).
72
+ def element_points
73
+ pts = []
74
+ (diagram.elements || []).each do |e|
75
+ primary = e.bounds || e.image_bounds
76
+ if primary
77
+ pts << [primary.x, primary.y]
78
+ pts << [primary.x + primary.width, primary.y + primary.height]
79
+ end
80
+ if e.bounds && e.image_bounds
81
+ ib = e.image_bounds
82
+ pts << [ib.x, ib.y]
83
+ pts << [ib.x + ib.width, ib.y + ib.height]
84
+ end
85
+ end
86
+ pts
87
+ end
88
+
89
+ # Package elements render with a tab polygon ABOVE the
90
+ # element's logical top edge (height PACKAGE_TAB_HEIGHT).
91
+ # Include those points so the canvas reserves space.
92
+ def package_tab_points
93
+ return [] if model_index.nil?
94
+
95
+ pts = []
96
+ (diagram.elements || []).each do |e|
97
+ ref = e.model_element_ref
98
+ next unless ref
99
+
100
+ candidate = model_index[ref]
101
+ next unless candidate.is_a?(Ea::Model::Package)
102
+
103
+ primary = e.bounds || e.image_bounds
104
+ next unless primary
105
+
106
+ pts << [primary.x, primary.y - PACKAGE_TAB_HEIGHT]
107
+ pts << [primary.x + primary.width, primary.y]
108
+ end
109
+ pts
110
+ end
111
+
112
+ def connector_points
113
+ pts = []
114
+ (diagram.connectors || []).each do |c|
115
+ (c.waypoints || []).each do |wp|
116
+ next unless wp.position
117
+
118
+ pts << [wp.position.x, wp.position.y]
119
+ end
120
+ end
121
+ pts
122
+ end
123
+
124
+ def marker_points
125
+ pts = []
126
+ (diagram.connectors || []).each do |c|
127
+ waypoints = (c.waypoints || []).map(&:position).compact
128
+ next unless waypoints.size >= 2
129
+
130
+ source = waypoints.first
131
+ target = waypoints.last
132
+ pts << [source.x - MARKER_EXTENT, source.y - MARKER_EXTENT]
133
+ pts << [source.x + MARKER_EXTENT, source.y + MARKER_EXTENT]
134
+ pts << [target.x - MARKER_EXTENT, target.y - MARKER_EXTENT]
135
+ pts << [target.x + MARKER_EXTENT, target.y + MARKER_EXTENT]
136
+ end
137
+ pts
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end