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