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,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Query
5
+ # Chainable query builder. Each method returns a new Builder
6
+ # with the predicate applied — no mutation. Final #call returns
7
+ # the filtered collection.
8
+ #
9
+ # Example:
10
+ # Ea::Query.new(model).classes.in_package("core")
11
+ # .with_stereotype("FeatureType").call
12
+ class Builder
13
+ attr_reader :model, :filters
14
+
15
+ def initialize(model, filters: [], kind: :objects)
16
+ @model = model
17
+ @filters = filters
18
+ @kind = kind
19
+ end
20
+
21
+ # Filter to UML classes only.
22
+ # @return [Builder]
23
+ def classes
24
+ with_type("Class")
25
+ end
26
+
27
+ # Filter to interfaces only.
28
+ # @return [Builder]
29
+ def interfaces
30
+ with_type("Interface")
31
+ end
32
+
33
+ # Filter to packages.
34
+ # @return [Builder]
35
+ def packages
36
+ Builder.new(model, filters: filters, kind: :packages)
37
+ end
38
+
39
+ # Filter to diagrams.
40
+ # @return [Builder]
41
+ def diagrams
42
+ Builder.new(model, filters: filters, kind: :diagrams)
43
+ end
44
+
45
+ # Filter by EA object_type (Class, Interface, Package, ...).
46
+ # @return [Builder]
47
+ def with_type(type)
48
+ Builder.new(model, filters: filters + [->(o) { o.object_type == type }], kind: @kind)
49
+ end
50
+
51
+ # Restrict to elements in the named package.
52
+ # @param name [String]
53
+ # @return [Builder]
54
+ def in_package(name)
55
+ pkg = (model.collections[:packages] || []).find { |p| p.name == name }
56
+ return self unless pkg
57
+
58
+ filter_by { |o| o.package_id == pkg.package_id }
59
+ end
60
+
61
+ # Filter by applied stereotype name.
62
+ # @param name [String]
63
+ # @return [Builder]
64
+ def with_stereotype(name)
65
+ filter_by do |o|
66
+ refs = o.respond_to?(:stereotype_refs) ? o.stereotype_refs : nil
67
+ refs&.any? { |r| r == name }
68
+ end
69
+ end
70
+
71
+ # Filter by name exact match.
72
+ # @param name [String]
73
+ # @return [Builder]
74
+ def named(name)
75
+ filter_by { |o| o.name == name }
76
+ end
77
+
78
+ # Filter by name substring (case-insensitive).
79
+ # @param substring [String]
80
+ # @return [Builder]
81
+ def name_contains(substring)
82
+ re = Regexp.new(Regexp.escape(substring), Regexp::IGNORECASE)
83
+ filter_by { |o| o.name&.match?(re) }
84
+ end
85
+
86
+ # Add a custom filter proc.
87
+ # @param block [Proc(Object)->Boolean]
88
+ # @return [Builder]
89
+ def filter_by(&block)
90
+ Builder.new(model, filters: filters + [block], kind: @kind)
91
+ end
92
+
93
+ # Materialize the filtered collection.
94
+ # @return [Array]
95
+ def call
96
+ records = model.collections[@kind] || []
97
+ records.select { |o| filters.all? { |f| f.call(o) } }
98
+ end
99
+
100
+ # Enumerable support.
101
+ include Enumerable
102
+
103
+ def each(&block)
104
+ call.each(&block)
105
+ end
106
+ end
107
+ end
108
+ end
data/lib/ea/query.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ # Query DSL namespace. Provides a chainable builder for filtering
5
+ # model elements. New predicates add new methods on the chain.
6
+ module Query
7
+ autoload :Builder, "ea/query/builder"
8
+ end
9
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Render
5
+ # Converts SVG strings to PNG and PDF using available system
6
+ # tools. Detects `rsvg-convert`, ImageMagick `convert`, and
7
+ # headless Chrome (`chromium --headless`). Raises a clear error
8
+ # when no converter is available.
9
+ class ImageConverter
10
+ attr_reader :svg
11
+
12
+ # @param svg [String] SVG markup
13
+ def initialize(svg)
14
+ @svg = svg
15
+ end
16
+
17
+ # @return [String] PNG bytes
18
+ def to_png
19
+ with_tempfile(".svg") do |svg_path|
20
+ File.write(svg_path, svg)
21
+ convert(svg_path, :png)
22
+ end
23
+ end
24
+
25
+ # @return [String] PDF bytes
26
+ def to_pdf
27
+ with_tempfile(".svg") do |svg_path|
28
+ File.write(svg_path, svg)
29
+ convert(svg_path, :pdf)
30
+ end
31
+ end
32
+
33
+ # Returns the first available converter executable, or nil.
34
+ # @return [Symbol, nil] :rsvg_convert | :magick | :chrome
35
+ def self.available_converter
36
+ return :rsvg_convert if system("which rsvg-convert > /dev/null 2>&1")
37
+ return :magick if system("which convert > /dev/null 2>&1")
38
+ return :chrome if chromium_executable
39
+
40
+ nil
41
+ end
42
+
43
+ def self.chromium_executable
44
+ %w[chromium chromium-browser google-chrome
45
+ /Applications/Chromium.app/Contents/MacOS/Chromium
46
+ /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome]
47
+ .find { |exe| File.executable?(exe) || system("which #{exe} > /dev/null 2>&1") }
48
+ end
49
+
50
+ private
51
+
52
+ def convert(svg_path, target)
53
+ converter = self.class.available_converter
54
+ raise Ea::Cli::Error,
55
+ "No SVG→#{target.upcase} converter available. " \
56
+ "Install rsvg-convert (librsvg) or ImageMagick." unless converter
57
+
58
+ with_tempfile(".#{target}") do |out_path|
59
+ run_converter(converter, svg_path, out_path, target)
60
+ File.binread(out_path)
61
+ end
62
+ end
63
+
64
+ def run_converter(name, svg_path, out_path, target)
65
+ case name
66
+ when :rsvg_convert
67
+ format_flag = target == :pdf ? "-f pdf" : "-f png"
68
+ system("rsvg-convert #{format_flag} -o #{shell_quote(out_path)} " \
69
+ "#{shell_quote(svg_path)}",
70
+ exception: true)
71
+ when :magick
72
+ system("convert #{shell_quote(svg_path)} #{shell_quote(out_path)}",
73
+ exception: true)
74
+ when :chrome
75
+ chrome = self.class.chromium_executable
76
+ system("#{shell_quote(chrome)} --headless --disable-gpu " \
77
+ "--print-to-pdf=#{shell_quote(out_path)} " \
78
+ "file://#{svg_path}",
79
+ exception: true)
80
+ end
81
+ end
82
+
83
+ # Quote a path for safe inclusion in a shell command. Only
84
+ # handles paths without single quotes (sufficient for tempfile
85
+ # paths which are numeric IDs under /tmp).
86
+ def shell_quote(path)
87
+ "'#{path}'"
88
+ end
89
+
90
+ def with_tempfile(suffix)
91
+ require "tmpdir"
92
+ Dir.mktmpdir("ea-render-") do |dir|
93
+ path = File.join(dir, "input#{suffix}")
94
+ yield path
95
+ ensure
96
+ File.delete(path) if File.exist?(path)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
data/lib/ea/render.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ # Conversion of SVG strings to raster/vector formats.
5
+ # Delegates to external tools (rsvg-convert, headless Chrome) when
6
+ # available; raises a clear error otherwise.
7
+ module Render
8
+ autoload :ImageConverter, "ea/render/image_converter"
9
+ end
10
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Shapescript
5
+ # Tokenizer + parser for ShapeScript. Returns a list of Shape
6
+ # primitives extracted from a single shape body.
7
+ #
8
+ # Supported grammar (extended):
9
+ #
10
+ # script := statement*
11
+ # statement := shape | var_decl | primitive | if_stmt | label
12
+ # shape := "shape" identifier "{" statement* "}"
13
+ # var_decl := "var" identifier "=" expr ";"
14
+ # primitive := call "(" args ")" ";"
15
+ # call := "rectangle" | "ellipse" | "polygon" |
16
+ # "line" | "path" | "label"
17
+ # args := expr ("," expr)*
18
+ # if_stmt := "if" "(" expr ")" "{" statement* "}"
19
+ # ("else" "{" statement* "}")?
20
+ # expr := identifier | number | string | binop
21
+ #
22
+ # Variables are resolved at parse time when possible; otherwise
23
+ # the identifier is left as a string for late binding.
24
+ module Parser
25
+ module_function
26
+
27
+ # @param source [String] ShapeScript source
28
+ # @return [Array<Shape>] flat list of all primitives across
29
+ # all shapes in the source. Shape nesting is flattened.
30
+ # Variables, conditionals, and labels are resolved/inlined
31
+ # where possible; unresolvable expressions are skipped.
32
+ def parse(source)
33
+ return [] if source.nil? || source.empty?
34
+
35
+ tokens = tokenize(source)
36
+ env = {}
37
+ shapes = []
38
+ consume_statements(tokens, env, shapes)
39
+ shapes
40
+ end
41
+
42
+ # -- Tokenizer ---------------------------------------------------
43
+
44
+ def tokenize(source)
45
+ clean = source.gsub(%r{//[^\n]*}, "")
46
+ .gsub(/\/\*.*?\*\//m, "")
47
+ clean.scan(/[A-Za-z_][A-Za-z0-9_]*|-?\d+\.?\d*|"[^"]*"|'[^']*'|[{}(),;=+\-*\/]/)
48
+ end
49
+
50
+ # Consume statements until matching close brace or end.
51
+ def consume_statements(tokens, env, shapes, top_level: true)
52
+ until tokens.empty?
53
+ tok = tokens.first
54
+ if tok == "}"
55
+ tokens.shift if top_level == false
56
+ break
57
+ end
58
+
59
+ tokens.shift # consume tok
60
+ case tok
61
+ when "shape"
62
+ _name = tokens.shift
63
+ _open = tokens.shift # "{"
64
+ consume_statements(tokens, env.dup, shapes, top_level: false)
65
+ when "var"
66
+ consume_var_decl(tokens, env)
67
+ when "if"
68
+ consume_if(tokens, env, shapes)
69
+ when "label"
70
+ shapes << consume_label(tokens, env)
71
+ else
72
+ shapes << consume_primitive(tok, tokens, env) if PRIMITIVES.key?(tok)
73
+ end
74
+ end
75
+ end
76
+
77
+ PRIMITIVES = {
78
+ "rectangle" => :rectangle,
79
+ "ellipse" => :ellipse,
80
+ "polygon" => :polygon,
81
+ "line" => :line,
82
+ "path" => :path
83
+ }.freeze
84
+
85
+ def consume_primitive(name, tokens, env)
86
+ kind = PRIMITIVES[name]
87
+ open_paren = tokens.shift # "("
88
+ params = []
89
+ if open_paren == "("
90
+ until tokens.empty?
91
+ tok = tokens.shift
92
+ break if tok == ")"
93
+
94
+ value = resolve_expr(tok, tokens, env)
95
+ params << value if value.is_a?(Numeric)
96
+ end
97
+ end
98
+ consume_semicolon(tokens)
99
+ Shape.new(kind: kind, params: params)
100
+ end
101
+
102
+ def consume_label(tokens, env)
103
+ tokens.shift # "("
104
+ text = tokens.shift
105
+ text = text.to_s.gsub(/\A["']|["']\z/, "")
106
+ tokens.shift until tokens.empty? || tokens.first == ")"
107
+ tokens.shift # ")"
108
+ consume_semicolon(tokens)
109
+ Shape.new(kind: :label, params: [text])
110
+ end
111
+
112
+ def consume_var_decl(tokens, env)
113
+ name = tokens.shift
114
+ eq = tokens.shift # "="
115
+ value = resolve_expr(tokens.shift, tokens, env)
116
+ consume_semicolon(tokens)
117
+ env[name] = value if value.is_a?(Numeric) || value.is_a?(String)
118
+ end
119
+
120
+ def consume_if(tokens, env, then_shapes)
121
+ tokens.shift # "("
122
+ condition = resolve_expr(tokens.shift, tokens, env)
123
+ tokens.shift until tokens.empty? || tokens.first == ")"
124
+ tokens.shift # ")"
125
+ tokens.shift # "{"
126
+
127
+ # Evaluate condition; if true, consume then-block into shapes.
128
+ # If false, skip then-block and consume optional else.
129
+ truthy = condition == true || (condition.is_a?(Numeric) && condition.nonzero?)
130
+ if truthy
131
+ consume_statements(tokens, env, then_shapes, top_level: false)
132
+ else
133
+ skip_block(tokens)
134
+ if tokens.first == "else"
135
+ tokens.shift
136
+ tokens.shift # "{"
137
+ consume_statements(tokens, env, then_shapes, top_level: false)
138
+ end
139
+ end
140
+ end
141
+
142
+ def skip_block(tokens)
143
+ tokens.shift # "{"
144
+ depth = 1
145
+ until tokens.empty? || depth.zero?
146
+ t = tokens.shift
147
+ depth += 1 if t == "{"
148
+ depth -= 1 if t == "}"
149
+ end
150
+ end
151
+
152
+ def consume_semicolon(tokens)
153
+ tokens.shift if tokens.first == ";"
154
+ end
155
+
156
+ # Resolve an expression to a value. Returns Numeric/String/Boolean
157
+ # or nil when unresolvable. Handles simple binary ops (+, -, *, /).
158
+ def resolve_expr(tok, tokens, env)
159
+ return nil unless tok
160
+
161
+ # Parenthesized sub-expression
162
+ if tok == "("
163
+ value = resolve_expr(tokens.shift, tokens, env)
164
+ tokens.shift if tokens.first == ")"
165
+ return value
166
+ end
167
+
168
+ # Numeric literal
169
+ return tok.to_i if tok.match?(/\A-?\d+\z/)
170
+ return tok.to_f if tok.match?(/\A-?\d+\.\d+\z/)
171
+
172
+ # String literal
173
+ return tok[1..-2] if tok.match?(/\A["'].*["']\z/)
174
+
175
+ # Boolean literals
176
+ return true if tok == "true"
177
+ return false if tok == "false"
178
+
179
+ # Variable reference
180
+ if env.key?(tok)
181
+ value = env[tok]
182
+ # Look ahead for binary op
183
+ if %w[+ - * /].include?(tokens.first)
184
+ op = tokens.shift
185
+ rhs = resolve_expr(tokens.shift, tokens, env)
186
+ return apply_binop(op, value, rhs)
187
+ end
188
+ return value
189
+ end
190
+
191
+ # Bare identifier (function call? hasproperty? — leave as nil
192
+ # for now; treat as false in conditionals).
193
+ nil
194
+ end
195
+
196
+ def apply_binop(op, left, right)
197
+ return nil unless left.is_a?(Numeric) && right.is_a?(Numeric)
198
+
199
+ case op
200
+ when "+" then left + right
201
+ when "-" then left - right
202
+ when "*" then left * right
203
+ when "/" then left / right
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Shapescript
5
+ # Renders a list of parsed Shape primitives to SVG elements as
6
+ # a single string. Coordinates are emitted as-is — callers
7
+ # should wrap in a `<g>` with the appropriate transform if they
8
+ # want to position the icon.
9
+ module Renderer
10
+ module_function
11
+
12
+ # @param shapes [Array<Shape>]
13
+ # @param fill [String] SVG fill color
14
+ # @param stroke [String] SVG stroke color
15
+ # @return [String] SVG fragment with one element per shape
16
+ def render(shapes, fill: "#FFFFFF", stroke: "#000000")
17
+ return "" if shapes.nil? || shapes.empty?
18
+
19
+ shapes.map { |s| render_one(s, fill: fill, stroke: stroke) }.join("\n")
20
+ end
21
+
22
+ def render_one(shape, fill:, stroke:)
23
+ case shape.kind
24
+ when :rectangle then render_rect(shape.params, fill, stroke)
25
+ when :ellipse then render_ellipse(shape.params, fill, stroke)
26
+ when :polygon then render_polygon(shape.params, fill, stroke)
27
+ when :line then render_line(shape.params, stroke)
28
+ when :path then render_path(shape.params, stroke)
29
+ when :label then render_label(shape.params, fill)
30
+ end
31
+ end
32
+
33
+ def render_label(p, fill)
34
+ text = p.first.to_s
35
+ %(<text x="0" y="0" fill="#{fill}">#{escape(text)}</text>)
36
+ end
37
+
38
+ def escape(text)
39
+ text.to_s.gsub("&", "&amp;")
40
+ .gsub("<", "&lt;")
41
+ .gsub(">", "&gt;")
42
+ end
43
+
44
+ def render_rect(p, fill, stroke)
45
+ x, y, w, h = p
46
+ %(<rect x="#{x}" y="#{y}" width="#{w}" height="#{h}" fill="#{fill}" stroke="#{stroke}"/>)
47
+ end
48
+
49
+ def render_ellipse(p, fill, stroke)
50
+ cx, cy, rx, ry = p
51
+ %(<ellipse cx="#{cx}" cy="#{cy}" rx="#{rx}" ry="#{ry}" fill="#{fill}" stroke="#{stroke}"/>)
52
+ end
53
+
54
+ def render_polygon(p, fill, stroke)
55
+ points = p.each_slice(2).map { |x, y| "#{x},#{y}" }.join(" ")
56
+ %(<polygon points="#{points}" fill="#{fill}" stroke="#{stroke}"/>)
57
+ end
58
+
59
+ def render_line(p, stroke)
60
+ x1, y1, x2, y2 = p
61
+ %(<line x1="#{x1}" y1="#{y1}" x2="#{x2}" y2="#{y2}" stroke="#{stroke}"/>)
62
+ end
63
+
64
+ def render_path(p, stroke)
65
+ return "" if p.empty?
66
+
67
+ x, y = p.shift(2)
68
+ d = "M #{x} #{y}"
69
+ p.each_slice(2) { |px, py| d += " L #{px} #{py}" }
70
+ %(<path d="#{d}" fill="none" stroke="#{stroke}"/>)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Shapescript
5
+ # One rendered shape primitive. Carries type + params
6
+ # ready for SVG emission.
7
+ Shape = Struct.new(:kind, :params, keyword_init: true) do
8
+ # @return [Boolean] true when this shape is a rectangle
9
+ def rectangle?
10
+ kind == :rectangle
11
+ end
12
+
13
+ def ellipse?
14
+ kind == :ellipse
15
+ end
16
+
17
+ def polygon?
18
+ kind == :polygon
19
+ end
20
+
21
+ def line?
22
+ kind == :line
23
+ end
24
+
25
+ def path?
26
+ kind == :path
27
+ end
28
+
29
+ def label?
30
+ kind == :label
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ # ShapeScript interpreter namespace.
5
+ #
6
+ # EA's MDG technologies define stereotype icon decorations via a
7
+ # domain-specific language called ShapeScript. This is a basic
8
+ # interpreter supporting the core shape primitives:
9
+ #
10
+ # shape <Name> { ... }
11
+ # rectangle(left, top, right, bottom);
12
+ # ellipse(left, top, right, bottom);
13
+ # polygon(x1, y1, x2, y2, ..., xN, yN);
14
+ # line(x1, y1, x2, y2);
15
+ # path(x1, y1, x2, y2, ...);
16
+ #
17
+ # Compound features (conditionals, variables, color references)
18
+ # are out of scope for this basic interpreter.
19
+ module Shapescript
20
+ autoload :Parser, "ea/shapescript/parser"
21
+ autoload :Renderer, "ea/shapescript/renderer"
22
+ autoload :Shape, "ea/shapescript/shape"
23
+ end
24
+ end
@@ -8,17 +8,24 @@ module Ea
8
8
  # and delegates to per-domain builders (OCP/MECE — new model
9
9
  # types or source columns touch one builder).
10
10
  class Adapter
11
- attr_reader :database, :qea_path
11
+ attr_reader :database, :qea_path, :mdg_registry
12
12
 
13
- def initialize(database, qea_path = nil)
13
+ def initialize(database, qea_path = nil, mdg_registry: nil)
14
14
  @database = database
15
15
  @qea_path = qea_path
16
+ @mdg_registry = mdg_registry
16
17
  end
17
18
 
18
19
  # Convenience: build a document directly from a .qea file
19
20
  # path. The file is parsed via the existing Ea::Qea pipeline.
20
- def self.from_path(qea_path)
21
- new(Ea.parse(qea_path), qea_path).to_document
21
+ #
22
+ # mdg_registry: an optional Ea::Mdg::Registry. When provided,
23
+ # ClassifierBuilder consults it to resolve inherited
24
+ # attributes from MDG-defined parent classes (e.g. phantom
25
+ # attributes like +lod1ImplicitRepresentation on CityGML
26
+ # ImplicitGeometry, whose stereotype maps to an MDG class).
27
+ def self.from_path(qea_path, mdg_registry: nil)
28
+ new(Ea.parse(qea_path), qea_path, mdg_registry: mdg_registry).to_document
22
29
  end
23
30
 
24
31
  def to_document
@@ -28,6 +35,8 @@ module Ea
28
35
  classifiers: classifiers,
29
36
  relationships: relationships,
30
37
  stereotypes: stereotypes,
38
+ notes: notes,
39
+ instance_specifications: instance_specifications,
31
40
  diagrams: diagrams
32
41
  )
33
42
  end
@@ -43,7 +52,7 @@ module Ea
43
52
  end
44
53
 
45
54
  def classifiers
46
- @classifiers ||= ClassifierBuilder.new(database).build_all
55
+ @classifiers ||= ClassifierBuilder.new(database, mdg_registry: mdg_registry).build_all
47
56
  end
48
57
 
49
58
  def relationships
@@ -54,6 +63,14 @@ module Ea
54
63
  @stereotypes ||= StereotypeBuilder.new(database).build_all
55
64
  end
56
65
 
66
+ def notes
67
+ @notes ||= NoteBuilder.new(database).build_all
68
+ end
69
+
70
+ def instance_specifications
71
+ @instance_specifications ||= InstanceBuilder.new(database).build_all
72
+ end
73
+
57
74
  def diagrams
58
75
  @diagrams ||= DiagramBuilder.new(database).build_all
59
76
  end