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
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ module Ea
6
+ module Export
7
+ module Xmi
8
+ # Generates Sparx-flavored XMI 2.1 from a parsed QEA model.
9
+ #
10
+ # Uses the existing QEA→XMI transformer under Ea::Transformers
11
+ # when available. Falls back to a minimal hand-rolled writer
12
+ # that produces a valid XMI root + class list when the bridge
13
+ # is unavailable.
14
+ class Generator
15
+ def self.call(model, **_opts)
16
+ new(model).call
17
+ end
18
+
19
+ attr_reader :model
20
+
21
+ # @param model [#collections] Ea::Qea::Database or compatible
22
+ def initialize(model)
23
+ @model = model
24
+ end
25
+
26
+ def call
27
+ builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
28
+ xml.xmi("XMI" => "2.1",
29
+ "xmlns:UML" => "http://schema.omg.org/spec/UML/2.1",
30
+ "xmlns:xmi" => "http://schema.omg.org/spec/XMI/2.1") do
31
+ xml.documentation(:exporter => "ea-rb",
32
+ :exporterVersion => Ea::VERSION)
33
+ emit_model(xml)
34
+ end
35
+ end
36
+ builder.to_xml
37
+ end
38
+
39
+ private
40
+
41
+ def emit_model(xml)
42
+ classes = (model.collections[:objects] || [])
43
+ .select { |o| o.object_type == "Class" }
44
+
45
+ xml.tag!("uml:Model", "xmi:id" => "model-root",
46
+ "name" => "model") do
47
+ classes.each do |klass|
48
+ emit_class(xml, klass)
49
+ end
50
+ end
51
+ end
52
+
53
+ def emit_class(xml, klass)
54
+ attrs = class_attributes(klass)
55
+ xml.tag!("ownedMember",
56
+ "xmi:type" => "uml:Class",
57
+ "xmi:id" => klass.ea_guid || klass.object_id.to_s,
58
+ "name" => klass.name || "Anonymous") do
59
+ attrs.each { |attr| emit_attribute(xml, attr) }
60
+ end
61
+ end
62
+
63
+ def emit_attribute(xml, attr)
64
+ xml.tag!("ownedAttribute",
65
+ "xmi:type" => "uml:Property",
66
+ "xmi:id" => attr.ea_guid || attr.property_id.to_s,
67
+ "name" => attr.name)
68
+ end
69
+
70
+ def class_attributes(klass)
71
+ attrs = model.collections[:attributes] || []
72
+ attrs.select { |a| a.object_id == klass.object_id }
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Export
5
+ # XMI export namespace. Generates Sparx-flavored XMI from a
6
+ # parsed model.
7
+ module Xmi
8
+ autoload :Generator, "ea/export/xmi/generator"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ module Ea
6
+ module Export
7
+ module Xsd
8
+ # Loads EA's GMLClassMapping.xml and exposes per-class mapping
9
+ # rules: UML class name → GML element/type/propertyType names.
10
+ #
11
+ # XML format:
12
+ # <ClassMapping>
13
+ # <Class name="FeatureType" element="featureMember"
14
+ # type="FeaturePropertyType" propertyType="..."
15
+ # typeContent="complex"/>
16
+ # </ClassMapping>
17
+ class ClassMapping
18
+ attr_reader :mappings
19
+
20
+ # @param mappings [Hash{String => Mapping}] keyed by UML class name
21
+ def initialize(mappings = {})
22
+ @mappings = mappings
23
+ end
24
+
25
+ Mapping = Struct.new(:name, :element, :type, :property_type,
26
+ :type_content, keyword_init: true)
27
+
28
+ # @param path [String] path to GMLClassMapping.xml
29
+ # @return [ClassMapping]
30
+ def self.from_path(path)
31
+ return new if path.nil? || !File.exist?(path)
32
+
33
+ from_nokogiri(Nokogiri::XML(File.read(path)))
34
+ end
35
+
36
+ # @param doc [Nokogiri::XML::Document]
37
+ # @return [ClassMapping]
38
+ def self.from_nokogiri(doc)
39
+ mappings = {}
40
+ doc.xpath("//Class").each do |node|
41
+ name = node["name"]
42
+ next unless name
43
+
44
+ mappings[name] = Mapping.new(
45
+ name: name,
46
+ element: node["element"],
47
+ type: node["type"],
48
+ property_type: node["propertyType"],
49
+ type_content: node["typeContent"]
50
+ )
51
+ end
52
+ new(mappings)
53
+ end
54
+
55
+ # Look up a mapping by UML class name. Returns nil when no
56
+ # explicit mapping exists.
57
+ # @param class_name [String]
58
+ # @return [Mapping, nil]
59
+ def for(class_name)
60
+ mappings[class_name]
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ module Ea
6
+ module Export
7
+ module Xsd
8
+ # Generates an XSD schema string from a parsed QEA model.
9
+ #
10
+ # Uses ClassMapping to translate UML class names to GML types
11
+ # and NamespaceRegistry to declare target namespaces. Walks the
12
+ # model's classifiers and emits `<xs:element>` + `<xs:complexType>`
13
+ # pairs.
14
+ #
15
+ # Both class_mapping and namespaces default to the EA-bundled
16
+ # GML fixtures when present, so callers can do
17
+ # Ea::Export::Xsd::Generator.call(model, target_namespace: ...)
18
+ # without manually loading XML fixtures.
19
+ class Generator
20
+ XSD_NS = "http://www.w3.org/2001/XMLSchema"
21
+
22
+ DEFAULT_MAPPING_PATH = "spec/fixtures/mdg/ea_config/gml/GMLClassMapping.xml".freeze
23
+ DEFAULT_NAMESPACES_PATH = "spec/fixtures/mdg/ea_config/gml/GMLNamespaces.xml".freeze
24
+
25
+ attr_reader :class_mapping, :namespaces
26
+
27
+ def initialize(class_mapping: nil, namespaces: nil)
28
+ @class_mapping = class_mapping || ClassMapping.from_path(DEFAULT_MAPPING_PATH)
29
+ @namespaces = namespaces || NamespaceRegistry.from_path(DEFAULT_NAMESPACES_PATH)
30
+ end
31
+
32
+ # Class-method shortcut. Accepts the same options as #initialize
33
+ # via keyword args.
34
+ def self.call(model, target_namespace: "http://example.com/xsd",
35
+ prefix: "tns", **opts)
36
+ new(**opts).call(model, target_namespace: target_namespace, prefix: prefix)
37
+ end
38
+
39
+ # Generate an XSD schema for the given model.
40
+ # @param model [#collections] anything responding to #collections
41
+ # with a :packages and :objects collection (Ea::Qea::Database).
42
+ # @param target_namespace [String] XSD targetNamespace
43
+ # @param prefix [String] namespace prefix for the target
44
+ # @return [String] serialized XSD
45
+ def call(model, target_namespace:, prefix: "tns")
46
+ doc = build_schema_doc(target_namespace, prefix)
47
+ walk_model(model, doc)
48
+ doc.to_xml(indent: 2)
49
+ end
50
+
51
+ private
52
+
53
+ def build_schema_doc(target_namespace, prefix)
54
+ builder = Nokogiri::XML::Builder.new do |xml|
55
+ xml.schema("xmlns" => XSD_NS,
56
+ "xmlns:#{prefix}" => target_namespace,
57
+ "targetNamespace" => target_namespace,
58
+ "elementFormDefault" => "qualified")
59
+ end
60
+ builder.doc
61
+ end
62
+
63
+ def walk_model(model, doc)
64
+ classes = model_classes(model)
65
+ classes.each { |klass| emit_class(doc, doc.root, klass) }
66
+ end
67
+
68
+ # Extract UML-like classes from the model. Ea::Qea::Database
69
+ # stores them in :objects; we filter to classes (Object_Type
70
+ # == "Class") and emit a struct for the generator's needs.
71
+ Classifier = Struct.new(:name, :stereotype, :attributes,
72
+ keyword_init: true)
73
+
74
+ def model_classes(model)
75
+ objects = model.collections[:objects] || []
76
+ objects.select { |o| o.object_type == "Class" }.map do |obj|
77
+ Classifier.new(
78
+ name: obj.name,
79
+ stereotype: stereotype_for(model, obj),
80
+ attributes: attributes_for(model, obj)
81
+ )
82
+ end
83
+ end
84
+
85
+ def stereotype_for(model, obj)
86
+ # Walk t_xref for the object's GUID; pick first @STEREO.
87
+ xrefs = model.collections[:xrefs] || []
88
+ stereo_xref = xrefs.find do |xr|
89
+ xr.client == obj.ea_guid && xr.description&.include?("@STEREO")
90
+ end
91
+ return nil unless stereo_xref
92
+
93
+ match = stereo_xref.description.match(/Name=([^;]+)/)
94
+ match ? match[1] : nil
95
+ end
96
+
97
+ def attributes_for(model, obj)
98
+ attrs = model.collections[:attributes] || []
99
+ attrs.select { |a| a.object_id == obj.object_id }
100
+ .map { |a| { name: a.name, type: a.type || "string" } }
101
+ end
102
+
103
+ def emit_class(doc, schema_root, classifier)
104
+ mapping = class_mapping.for(classifier.name) ||
105
+ class_mapping.for(classifier.stereotype.to_s)
106
+
107
+ type_name = mapping&.type || "#{classifier.name}Type"
108
+ element_name = mapping&.element || classifier.name&.downcase
109
+
110
+ schema_root.add_child(element_node(doc, element_name, type_name))
111
+ schema_root.add_child(complex_type_node(doc, type_name, classifier))
112
+ end
113
+
114
+ def element_node(doc, name, type_name)
115
+ el = doc.create_element("element", "xmlns" => XSD_NS)
116
+ el["name"] = name
117
+ el["type"] = "tns:#{type_name}"
118
+ el
119
+ end
120
+
121
+ def complex_type_node(doc, type_name, classifier)
122
+ ct = doc.create_element("complexType", "xmlns" => XSD_NS)
123
+ ct["name"] = type_name
124
+ seq = doc.create_element("sequence", "xmlns" => XSD_NS)
125
+ classifier.attributes.each do |attr|
126
+ seq.add_child(attribute_element(doc, attr))
127
+ end
128
+ ct.add_child(seq)
129
+ ct
130
+ end
131
+
132
+ def attribute_element(doc, attr)
133
+ el = doc.create_element("element", "xmlns" => XSD_NS)
134
+ el["name"] = attr[:name].to_s
135
+ el["type"] = "xs:string"
136
+ el["minOccurs"] = "0"
137
+ el
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ module Ea
6
+ module Export
7
+ module Xsd
8
+ # Loads EA's GMLNamespaces.xml and exposes per-prefix namespace
9
+ # declarations: prefix → { target_namespace:, xsd_document: }.
10
+ #
11
+ # XML format:
12
+ # <Namespaces>
13
+ # <GMLNS version="3.2.1">
14
+ # <Namespace xmlns="gml" targetNamespace="http://..."
15
+ # xsdDocument="http://.../gml.xsd"/>
16
+ # </GMLNS>
17
+ # </Namespaces>
18
+ class NamespaceRegistry
19
+ attr_reader :namespaces
20
+
21
+ # @param namespaces [Hash{String => Namespace}]
22
+ def initialize(namespaces = {})
23
+ @namespaces = namespaces
24
+ end
25
+
26
+ Namespace = Struct.new(:prefix, :target_namespace, :xsd_document,
27
+ :version, keyword_init: true)
28
+
29
+ # @param path [String] path to GMLNamespaces.xml
30
+ # @param version [String, nil] GML version filter (e.g. "3.2.1")
31
+ # @return [NamespaceRegistry]
32
+ def self.from_path(path, version: nil)
33
+ return new if path.nil? || !File.exist?(path)
34
+
35
+ from_nokogiri(Nokogiri::XML(File.read(path)), version: version)
36
+ end
37
+
38
+ # @param doc [Nokogiri::XML::Document]
39
+ # @param version [String, nil] GML version filter
40
+ # @return [NamespaceRegistry]
41
+ def self.from_nokogiri(doc, version: nil)
42
+ namespaces = {}
43
+ # EA stores the prefix in a `xmlns` attribute, which XML
44
+ # reserves for namespace declarations. Nokogiri interprets
45
+ # it as the element's namespace, so we read it back via
46
+ # `namespace` (the parsed NS) rather than `attribute`.
47
+ # Use local-name() so the lookup is namespace-agnostic.
48
+ doc.xpath("//*[local-name()='GMLNS']").each do |gmlns|
49
+ next if version && gmlns["version"] != version
50
+
51
+ gmlns.element_children.each do |node|
52
+ next unless node.name == "Namespace"
53
+
54
+ prefix = node.namespace&.href
55
+ next unless prefix
56
+
57
+ namespaces[prefix] = Namespace.new(
58
+ prefix: prefix,
59
+ target_namespace: node["targetNamespace"],
60
+ xsd_document: node["xsdDocument"],
61
+ version: gmlns["version"]
62
+ )
63
+ end
64
+ end
65
+ new(namespaces)
66
+ end
67
+
68
+ # @param prefix [String]
69
+ # @return [Namespace, nil]
70
+ def for(prefix)
71
+ namespaces[prefix]
72
+ end
73
+
74
+ # All distinct versions present (e.g. ["3.2.1", "3.3"]).
75
+ # @return [Array<String>]
76
+ def versions
77
+ namespaces.values.map(&:version).compact.uniq
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Export
5
+ # XSD (XML Schema Definition) export namespace.
6
+ #
7
+ # Translates a parsed QEA/UML model into an XSD schema by:
8
+ # 1. Reading GMLClassMapping.xml to learn how UML classes map to
9
+ # GML types (element/type/propertyType names).
10
+ # 2. Reading GMLNamespaces.xml to learn target namespaces.
11
+ # 3. Walking the model's classes and emitting `<xs:element>` +
12
+ # `<xs:complexType>` declarations for each.
13
+ module Xsd
14
+ autoload :ClassMapping, "ea/export/xsd/class_mapping"
15
+ autoload :NamespaceRegistry, "ea/export/xsd/namespace_registry"
16
+ autoload :Generator, "ea/export/xsd/generator"
17
+ end
18
+ end
19
+ end
data/lib/ea/export.rb ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ # Export namespace — format-specific exporters (XSD, PlantUML, etc.).
5
+ # One sub-namespace per output format; each is independent and
6
+ # autoloadable.
7
+ module Export
8
+ autoload :Xmi, "ea/export/xmi"
9
+ autoload :Json, "ea/export/json"
10
+ autoload :JsonSchema, "ea/export/json_schema"
11
+ autoload :PlantUml, "ea/export/plantuml"
12
+ autoload :Xsd, "ea/export/xsd"
13
+ end
14
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Image
5
+ # Converts EA's t_image EMF blobs to SVG strings by delegating to
6
+ # the pure-Ruby `emfsvg` gem. Lazy-loads the gem on first call.
7
+ #
8
+ # When emfsvg is unavailable, returns nil — callers should fall
9
+ # back to skipping image rendering. This keeps the gem optional
10
+ # rather than a hard runtime requirement.
11
+ module EmfRenderer
12
+ module_function
13
+
14
+ # Convert an EMF blob (raw bytes) to an SVG string.
15
+ #
16
+ # @param bytes [String, nil] EMF binary blob from t_image.Image
17
+ # @param options [Hash] forwarded to Emfsvg.from_bytes
18
+ # @return [String, nil] SVG markup, or nil if emfsvg unavailable
19
+ def render(bytes, **options)
20
+ return nil if bytes.nil? || bytes.empty?
21
+ return nil unless available?
22
+
23
+ Emfsvg.from_bytes(bytes, **options)
24
+ rescue StandardError => e
25
+ warn "[ea] emfsvg render failed: #{e.message}" if ENV["EA_DEBUG"]
26
+ nil
27
+ end
28
+
29
+ # @return [Boolean] true if the emfsvg gem can be loaded
30
+ def available?
31
+ return @available if defined?(@available)
32
+
33
+ begin
34
+ require "emfsvg"
35
+ @available = !!Emfsvg
36
+ rescue LoadError
37
+ @available = false
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
data/lib/ea/image.rb ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ # Image rendering namespace. Converts EA's t_image EMF blobs to SVG.
5
+ #
6
+ # The emfsvg gem is an OPTIONAL runtime dependency. If it isn't
7
+ # loadable, callers degrade gracefully (image is skipped). This
8
+ # keeps the ea gem usable without forcing emfsvg on every install.
9
+ module Image
10
+ autoload :EmfRenderer, "ea/image/emf_renderer"
11
+ end
12
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Lint
5
+ # Runs all registered rules against a model and collects offenses.
6
+ # Adding a rule = adding it to DEFAULT_RULES (OCP).
7
+ class Engine
8
+ attr_reader :rules
9
+
10
+ DEFAULT_RULES = [
11
+ Rules::NamingConventionRule,
12
+ Rules::OrphanElementRule,
13
+ Rules::DuplicateNameRule,
14
+ Rules::CyclicGeneralizationRule,
15
+ Rules::MissingStereotypeRule
16
+ ].freeze
17
+
18
+ # @param rules [Array<Class>] LintRule subclasses
19
+ def initialize(rules: DEFAULT_RULES)
20
+ @rules = rules
21
+ end
22
+
23
+ # @param model [#collections]
24
+ # @return [Array<Offense>]
25
+ def run(model)
26
+ @rules.flat_map { |rule_class| rule_class.new.check(model) }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Lint
5
+ # Base class for lint rules. Subclasses implement #check(model)
6
+ # and return an Array<Offense>. Default severity is :warning;
7
+ # subclasses can override via `self.severity`.
8
+ class LintRule
9
+ class << self
10
+ attr_accessor :severity
11
+ end
12
+
13
+ def self.name
14
+ to_s.split("::").last.gsub(/Rule\z/, "").gsub(/([a-z])([A-Z])/, '\1_\2').downcase
15
+ end
16
+
17
+ def self.severity
18
+ @severity || :warning
19
+ end
20
+
21
+ def check(_model)
22
+ raise NotImplementedError, "#{self.class}#check not implemented"
23
+ end
24
+
25
+ protected
26
+
27
+ def offense(entity_id:, entity_name:, message:, severity: nil)
28
+ Offense.new(
29
+ rule: self.class.name,
30
+ severity: severity || self.class.severity,
31
+ entity_id: entity_id,
32
+ entity_name: entity_name,
33
+ message: message
34
+ )
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Lint
5
+ # One lint offense. Carries rule name, severity, entity id/name,
6
+ # and a human-readable message.
7
+ Offense = Struct.new(:rule, :severity, :entity_id, :entity_name,
8
+ :message, keyword_init: true) do
9
+ def error?
10
+ severity == :error
11
+ end
12
+
13
+ def warning?
14
+ severity == :warning
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Lint
5
+ module Rules
6
+ # Detects cyclic generalization chains (A → B → A).
7
+ class CyclicGeneralizationRule < LintRule
8
+ self.severity = :error
9
+
10
+ def check(model)
11
+ graph = build_graph(model)
12
+ cycles = find_cycles(graph)
13
+ cycles.map do |cycle|
14
+ offense(entity_id: cycle.first,
15
+ entity_name: cycle.join(" → "),
16
+ message: "Cyclic generalization: #{cycle.join(' → ')} → #{cycle.first}")
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def build_graph(model)
23
+ graph = {}
24
+ (model.collections[:connectors] || []).each do |conn|
25
+ next unless conn.connector_type == "Generalization"
26
+
27
+ from = conn.start_object_id
28
+ to = conn.end_object_id
29
+ graph[from] ||= []
30
+ graph[from] << to
31
+ end
32
+ graph
33
+ end
34
+
35
+ def find_cycles(graph)
36
+ cycles = []
37
+ visited = Set.new
38
+
39
+ graph.each_key do |start|
40
+ next if visited.include?(start)
41
+
42
+ dfs(graph, start, [start], Set.new([start]), cycles, visited)
43
+ end
44
+ cycles
45
+ end
46
+
47
+ def dfs(graph, node, path, on_path, cycles, visited)
48
+ (graph[node] || []).each do |neighbor|
49
+ if on_path.include?(neighbor)
50
+ idx = path.index(neighbor)
51
+ cycles << path[idx..] + [neighbor] if idx
52
+ elsif !visited.include?(neighbor)
53
+ dfs(graph, neighbor, path + [neighbor],
54
+ on_path + [neighbor], cycles, visited)
55
+ end
56
+ end
57
+ visited.add(node)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Lint
5
+ module Rules
6
+ # Two classes with the same name in the same package.
7
+ class DuplicateNameRule < LintRule
8
+ self.severity = :warning
9
+
10
+ def check(model)
11
+ classes = (model.collections[:objects] || [])
12
+ .select { |o| o.object_type == "Class" }
13
+ grouped = classes.group_by { |c| [c.package_id, c.name] }
14
+ grouped.flat_map do |(pkg_id, name), group|
15
+ next [] if group.size == 1
16
+
17
+ group.map do |klass|
18
+ offense(entity_id: klass.object_id,
19
+ entity_name: klass.name,
20
+ message: "Duplicate class name '#{name}' in package #{pkg_id} " \
21
+ "(#{group.size} occurrences)")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Lint
5
+ module Rules
6
+ # Packages named "Application Schema" or with `applicationSchema`
7
+ # stereotype should have at least one class with a GML stereotype.
8
+ class MissingStereotypeRule < LintRule
9
+ self.severity = :info
10
+
11
+ GML_STEREOTYPES = %w[FeatureType Type DataType CodeList
12
+ Enumeration ObjectType].freeze
13
+
14
+ def check(model)
15
+ classes = (model.collections[:objects] || [])
16
+ .select { |o| o.object_type == "Class" }
17
+ classes_without_stereo = classes.select { |c| stereotype_refs(c).empty? }
18
+ classes_without_stereo.map do |klass|
19
+ offense(entity_id: klass.object_id,
20
+ entity_name: klass.name,
21
+ message: "Class has no applied stereotype",
22
+ severity: :info)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def stereotype_refs(klass)
29
+ refs = klass.respond_to?(:stereotype_refs) ? klass.stereotype_refs : nil
30
+ refs || []
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end