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
@@ -1,333 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "lutaml/model"
4
- require "yaml"
5
-
6
- module Ea
7
- module Transformations
8
- # Configuration service for model transformations using external YAML
9
- # configuration.
10
- #
11
- # This class follows the Dependency Inversion Principle by allowing external
12
- # configuration instead of hardcoded behavior. It uses lutaml-model for
13
- # structured YAML parsing and validation.
14
- #
15
- # @example Load default configuration
16
- # config = Configuration.load
17
- # puts config.enabled_formats
18
- #
19
- # @example Load custom configuration
20
- # config = Configuration.load("my_config.yml")
21
- # parser_config = config.parser_config_for("xmi")
22
- class Configuration < Lutaml::Model::Serializable
23
- # Parser configuration model
24
- class ParserConfig < Lutaml::Model::Serializable
25
- attribute :format, :string
26
- attribute :extension, :string
27
- attribute :parser_class, :string
28
- attribute :enabled, :boolean, default: -> { true }
29
- attribute :priority, :integer, default: -> { 100 }
30
- attribute :description, :string
31
- attribute :options, :string, collection: true
32
-
33
- yaml do
34
- map "format", to: :format
35
- map "extension", to: :extension
36
- map "parser_class", to: :parser_class
37
- map "enabled", to: :enabled
38
- map "priority", to: :priority
39
- map "description", to: :description
40
- map "options", to: :options
41
- end
42
-
43
- # Check if this parser handles the given extension
44
- #
45
- # @param ext [String] File extension (e.g., ".xmi")
46
- # @return [Boolean] true if this parser handles the extension
47
- def handles_extension?(ext)
48
- extension == ext.downcase
49
- end
50
- end
51
-
52
- # Transformation options model
53
- class TransformationOptions < Lutaml::Model::Serializable
54
- attribute :validate_output, :boolean, default: -> { false }
55
- attribute :include_diagrams, :boolean, default: -> { true }
56
- attribute :preserve_ids, :boolean, default: -> { true }
57
- attribute :resolve_references, :boolean, default: -> { true }
58
- attribute :strict_mode, :boolean, default: -> { false }
59
-
60
- yaml do
61
- map "validate_output", to: :validate_output
62
- map "include_diagrams", to: :include_diagrams
63
- map "preserve_ids", to: :preserve_ids
64
- map "resolve_references", to: :resolve_references
65
- map "strict_mode", to: :strict_mode
66
- end
67
- end
68
-
69
- # Format detection rules model
70
- class FormatDetection < Lutaml::Model::Serializable
71
- attribute :use_file_extension, :boolean, default: -> { true }
72
- attribute :use_content_sniffing, :boolean, default: -> { true }
73
- attribute :fallback_parser, :string
74
- attribute :magic_bytes, :string, collection: true
75
-
76
- yaml do
77
- map "use_file_extension", to: :use_file_extension
78
- map "use_content_sniffing", to: :use_content_sniffing
79
- map "fallback_parser", to: :fallback_parser
80
- map "magic_bytes", to: :magic_bytes
81
- end
82
- end
83
-
84
- # Error handling configuration
85
- class ErrorHandling < Lutaml::Model::Serializable
86
- attribute :strategy, :string, default: -> { "continue" }
87
- attribute :log_errors, :boolean, default: -> { true }
88
- attribute :max_errors, :integer, default: -> { 10 }
89
- attribute :fail_fast, :boolean, default: -> { false }
90
-
91
- yaml do
92
- map "strategy", to: :strategy
93
- map "log_errors", to: :log_errors
94
- map "max_errors", to: :max_errors
95
- map "fail_fast", to: :fail_fast
96
- end
97
- end
98
-
99
- attribute :version, :string
100
- attribute :description, :string
101
- attribute :parsers, ParserConfig, collection: true
102
- attribute :transformation_options, TransformationOptions
103
- attribute :format_detection, FormatDetection
104
- attribute :error_handling, ErrorHandling
105
-
106
- yaml do
107
- map "version", to: :version
108
- map "description", to: :description
109
- map "parsers", to: :parsers
110
- map "transformation_options", to: :transformation_options
111
- map "format_detection", to: :format_detection
112
- map "error_handling", to: :error_handling
113
- end
114
-
115
- class << self
116
- # Load configuration from YAML file
117
- #
118
- # @param config_path [String, nil] Path to configuration file
119
- # Defaults to config/model_transformations.yml
120
- # @return [Configuration] The loaded configuration
121
- # @raise [Errno::ENOENT] if config file not found
122
- # @raise [Lutaml::Model::Error] if YAML is invalid
123
- def load(config_path = nil)
124
- config_path ||= default_config_path
125
-
126
- unless File.exist?(config_path)
127
- # Create default configuration if none exists
128
- return create_default_configuration
129
- end
130
-
131
- yaml_content = File.read(config_path)
132
- from_yaml(yaml_content)
133
- end
134
-
135
- # Get default configuration file path
136
- #
137
- # @return [String] Path to default config file
138
- def default_config_path
139
- File.expand_path("../../../config/model_transformations.yml", __dir__)
140
- end
141
-
142
- # Create default configuration when no config file exists
143
- #
144
- # @return [Configuration] Default configuration instance
145
- def create_default_configuration
146
- new.tap do |config|
147
- config.version = "1.0"
148
- config.description = "Default Model Transformations Configuration"
149
-
150
- # Default parsers
151
- config.parsers = [
152
- create_xmi_parser_config,
153
- create_qea_parser_config,
154
- ]
155
-
156
- # Default options
157
- config.transformation_options = TransformationOptions.new
158
- config.format_detection = FormatDetection.new
159
- config.error_handling = ErrorHandling.new
160
- end
161
- end
162
-
163
- private
164
-
165
- # Create default XMI parser configuration
166
- #
167
- # @return [ParserConfig] XMI parser configuration
168
- def create_xmi_parser_config
169
- ParserConfig.new.tap do |parser|
170
- parser.format = "xmi"
171
- parser.extension = ".xmi"
172
- parser.parser_class = "Ea::Transformations::Parsers::XmiParser"
173
- parser.enabled = true
174
- parser.priority = 100
175
- parser.description = "XML Metadata Interchange parser"
176
- parser.options = ["validate_xml", "resolve_references"]
177
- end
178
- end
179
-
180
- # Create default QEA parser configuration
181
- #
182
- # @return [ParserConfig] QEA parser configuration
183
- def create_qea_parser_config
184
- ParserConfig.new.tap do |parser|
185
- parser.format = "qea"
186
- parser.extension = ".qea"
187
- parser.parser_class = "Ea::Transformations::Parsers::QeaParser"
188
- parser.enabled = true
189
- parser.priority = 90
190
- parser.description = "Enterprise Architect database parser"
191
- parser.options = ["include_diagrams", "resolve_references"]
192
- end
193
- end
194
- end
195
-
196
- # Get list of enabled parsers, sorted by priority
197
- #
198
- # @return [Array<ParserConfig>] Array of enabled parser configurations
199
- def enabled_parsers
200
- parsers&.select(&:enabled)&.sort_by { |p| -p.priority } || []
201
- end
202
-
203
- # Get parser configuration by format name
204
- #
205
- # @param format [String] The format name (e.g., "xmi", "qea")
206
- # @return [ParserConfig, nil] The parser configuration or nil if not found
207
- def parser_config_for(format)
208
- parsers&.find { |p| p.format == format.downcase }
209
- end
210
-
211
- # Get parser configuration by file extension
212
- #
213
- # @param extension [String] The file extension (e.g., ".xmi", ".qea")
214
- # @return [ParserConfig, nil] The parser configuration or nil if not found
215
- def parser_config_for_extension(extension)
216
- normalized_ext = extension.downcase
217
- unless normalized_ext.start_with?(".")
218
- normalized_ext = ".#{normalized_ext}"
219
- end
220
-
221
- enabled_parsers.find { |p| p.handles_extension?(normalized_ext) }
222
- end
223
-
224
- # Check if a format is enabled
225
- #
226
- # @param format [String] The format name
227
- # @return [Boolean] true if format is enabled
228
- def format_enabled?(format)
229
- parser = parser_config_for(format)
230
- parser&.enabled == true
231
- end
232
-
233
- # Get all enabled format names
234
- #
235
- # @return [Array<String>] Array of enabled format names
236
- def enabled_formats
237
- enabled_parsers.map(&:format)
238
- end
239
-
240
- # Get all supported file extensions
241
- #
242
- # @return [Array<String>] Array of supported extensions
243
- def supported_extensions
244
- enabled_parsers.filter_map(&:extension)
245
- end
246
-
247
- # Check if content sniffing is enabled
248
- #
249
- # @return [Boolean] true if content sniffing should be used
250
- def content_sniffing_enabled?
251
- format_detection&.use_content_sniffing == true
252
- end
253
-
254
- # Check if file extension detection is enabled
255
- #
256
- # @return [Boolean] true if file extension should be used for detection
257
- def file_extension_detection_enabled?
258
- format_detection&.use_file_extension == true
259
- end
260
-
261
- # Get fallback parser when format detection fails
262
- #
263
- # @return [String, nil] The fallback parser class name
264
- def fallback_parser
265
- format_detection&.fallback_parser
266
- end
267
-
268
- # Get transformation options with defaults
269
- #
270
- # @return [TransformationOptions] Transformation options
271
- def transformation_options
272
- @transformation_options ||= TransformationOptions.new
273
- end
274
-
275
- # Get error handling configuration with defaults
276
- #
277
- # @return [ErrorHandling] Error handling configuration
278
- def error_handling
279
- @error_handling ||= ErrorHandling.new
280
- end
281
-
282
- # Merge with another configuration (this takes precedence)
283
- #
284
- # @param other [Configuration] Configuration to merge with
285
- # @return [Configuration] New merged configuration
286
- def merge(other) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
287
- merged = self.class.new
288
-
289
- # Basic attributes
290
- merged.version = version || other.version
291
- merged.description = description || other.description
292
-
293
- # Merge parsers (this config takes precedence)
294
- merged.parsers = merge_parsers(other.parsers)
295
-
296
- # Use this config's options, fallback to other
297
- merged.transformation_options = transformation_options ||
298
- other.transformation_options
299
- merged.format_detection = format_detection || other.format_detection
300
- merged.error_handling = error_handling || other.error_handling
301
-
302
- merged
303
- end
304
-
305
- private
306
-
307
- # Merge parser configurations
308
- #
309
- # @param other_parsers [Array<ParserConfig>] Other parsers to merge
310
- # @return [Array<ParserConfig>] Merged parser list
311
- def merge_parsers(other_parsers) # rubocop:disable Metrics/MethodLength
312
- return parsers unless other_parsers
313
- return other_parsers unless parsers
314
-
315
- # Create a hash of this config's parsers by format
316
- our_parsers = {}
317
- parsers.each do |parser|
318
- our_parsers[parser.format] = parser
319
- end
320
-
321
- # Add other parsers that we don't have
322
- merged = parsers.dup
323
- other_parsers.each do |other_parser|
324
- unless our_parsers.key?(other_parser.format)
325
- merged << other_parser
326
- end
327
- end
328
-
329
- merged
330
- end
331
- end
332
- end
333
- end
@@ -1,366 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ea
4
- module Transformations
5
- # Format Registry manages parser registration and discovery.
6
- #
7
- # This class implements the Registry pattern to provide a centralized
8
- # location for managing format parsers. It follows the Open/Closed Principle
9
- # by allowing new parsers to be registered without modifying existing code.
10
- #
11
- # @example Register a parser
12
- # registry = FormatRegistry.new
13
- # registry.register(".custom", MyCustomParser)
14
- #
15
- # @example Get parser for extension
16
- # parser_class = registry.parser_for_extension(".xmi")
17
- # parser = parser_class.new
18
- class FormatRegistry
19
- # @return [Hash<String, Class>] Map of extensions to parser classes
20
- attr_reader :parsers
21
-
22
- def initialize
23
- @parsers = {}
24
- @extensions = []
25
- @default_parsers_loaded = false
26
- end
27
-
28
- # Register a parser for a file extension
29
- #
30
- # @param extension [String] File extension (e.g., ".xmi", ".qea")
31
- # @param parser_class [Class] Parser class implementing BaseParser
32
- # interface
33
- # @raise [ArgumentError] if extension or parser_class is invalid
34
- def register(extension, parser_class) # rubocop:disable Metrics/MethodLength
35
- if extension.is_a?(Array)
36
- extension.each { |ext| register(ext, parser_class) }
37
- return
38
- end
39
-
40
- validate_extension!(extension)
41
- validate_parser_class!(parser_class)
42
-
43
- normalized_ext = normalize_extension(extension)
44
- @parsers[normalized_ext] = parser_class
45
- unless @extensions.include?(normalized_ext)
46
- @extensions << normalized_ext
47
- end
48
- end
49
-
50
- # Unregister a parser for a file extension
51
- #
52
- # @param extension [String] File extension to unregister
53
- # @return [Class, nil] The unregistered parser class, or nil if not found
54
- def unregister(extension)
55
- normalized_ext = normalize_extension(extension)
56
- @extensions.delete(normalized_ext)
57
- @parsers.delete(normalized_ext)
58
- end
59
-
60
- # Auto-register a parser class based on its supported extensions
61
- #
62
- # @param parser_class [Class] Parser class inherited from BaseParser
63
- # @return [void]
64
- def auto_register_from_parser(parser_class)
65
- unless parser_class.is_a?(Class) &&
66
- parser_class <= Ea::Transformations::Parsers::BaseParser
67
- raise ArgumentError,
68
- "#{parser_class} must inherit from " \
69
- "Ea::Transformations::Parsers::BaseParser"
70
- end
71
-
72
- extensions = parser_class.new.supported_extensions
73
- raise ArgumentError, "Extension cannot be nil or empty" if extensions.nil? || extensions.empty?
74
-
75
- register(extensions, parser_class)
76
- end
77
-
78
- # Get parser class for a file extension
79
- #
80
- # @param extension [String] File extension (e.g., ".xmi", ".qea")
81
- # @return [Class, nil] Parser class, or nil if not found
82
- def parser_for_extension(extension)
83
- # ensure_default_parsers_loaded!
84
- normalized_ext = normalize_extension(extension)
85
- @parsers[normalized_ext]
86
- end
87
-
88
- # Get parser class for a file path
89
- #
90
- # @param file_path [String] Path to the file
91
- # @return [Class, nil] Parser class, or nil if not found
92
- def parser_for_file(file_path)
93
- extension = File.extname(file_path)
94
- parser_for_extension(extension)
95
- end
96
-
97
- # Check if an extension is supported
98
- #
99
- # @param extension [String] File extension to check
100
- # @return [Boolean] true if extension is supported
101
- def supports_extension?(extension)
102
- parser_for_extension(extension) != nil
103
- end
104
-
105
- # Check if a file is supported
106
- #
107
- # @param file_path [String] Path to the file
108
- # @return [Boolean] true if file format is supported
109
- def supports_file?(file_path)
110
- parser_for_file(file_path) != nil
111
- end
112
-
113
- # Get all supported extensions
114
- #
115
- # @return [Array<String>] List of supported file extensions
116
- def supported_extensions
117
- # ensure_default_parsers_loaded!
118
- @parsers.keys.sort
119
- end
120
-
121
- # Get all registered parsers
122
- #
123
- # @return [Hash<String, Class>] Map of extensions to parser classes
124
- def all_parsers
125
- # ensure_default_parsers_loaded!
126
- @parsers.dup
127
- end
128
-
129
- # Get parsers sorted by priority (highest first)
130
- #
131
- # @return [Array<Array(String, Class)>] List of [extension, parser_class]
132
- def parsers_by_priority
133
- @parsers.sort_by do |_ext, parser_class|
134
- parser_class.new.priority
135
- end.reverse
136
- end
137
-
138
- # Get all registered extensions
139
- #
140
- # @return [Array<String>] List of registered extensions
141
- def all_extensions
142
- @extensions.dup
143
- end
144
-
145
- # Get statistics about registered parsers
146
- #
147
- # @return [Hash] Statistics hash
148
- def statistics # rubocop:disable Metrics/MethodLength
149
- parsers = @parsers.values.uniq
150
- total_parsers = parsers.size
151
- ext_size = @extensions.size
152
-
153
- {
154
- total_parsers: total_parsers,
155
- total_extensions: ext_size,
156
- extensions_per_parser: (ext_size.to_f / total_parsers).round(2),
157
- parser_details: parsers.map do |parser_class|
158
- {
159
- parser: parser_class,
160
- extensions: parser_class.new.supported_extensions,
161
- priority: parser_class.new.priority,
162
- format_name: parser_class.new.format_name,
163
- }
164
- end,
165
- }
166
- end
167
-
168
- def export_configuration # rubocop:disable Metrics/MethodLength
169
- {
170
- exported_at: Time.now,
171
- parsers: @parsers.map do |parser|
172
- _ext, parser_class = parser
173
-
174
- {
175
- parser_class: parser_class,
176
- extensions: parser_class.new.supported_extensions,
177
- priority: parser_class.new.priority,
178
- format: parser_class.new.format_name,
179
- }
180
- end,
181
- }
182
- end
183
-
184
- # Clear all registered parsers
185
- #
186
- # @return [void]
187
- def clear
188
- @extensions.clear
189
- @parsers.clear
190
- @default_parsers_loaded = false
191
- end
192
-
193
- # Load parsers from configuration
194
- #
195
- # @param configuration [Configuration] Configuration with parser
196
- # definitions
197
- # @return [void]
198
- def load_from_configuration(configuration)
199
- configuration.enabled_parsers.each do |parser_config|
200
- parser_class = constantize_parser_class(parser_config.parser_class)
201
- register(parser_config.extension, parser_class)
202
- rescue NameError => e
203
- warn "Warning: Could not load parser " \
204
- "#{parser_config.parser_class}: #{e.message}"
205
- end
206
- end
207
-
208
- # Create a new instance with default parsers loaded
209
- #
210
- # @return [FormatRegistry] New registry instance
211
- def self.with_defaults
212
- registry = new
213
- registry.load_default_parsers
214
- registry
215
- end
216
-
217
- # Load default parsers (called automatically when needed)
218
- #
219
- # @return [void]
220
- def load_default_parsers # rubocop:disable Metrics/MethodLength
221
- return if @default_parsers_loaded
222
-
223
- # Load XMI parser if available
224
- begin
225
- register(".xmi", Parsers::XmiParser)
226
- rescue LoadError
227
- # XMI parser not available, skip
228
- end
229
-
230
- # Load QEA parser if available
231
- begin
232
- register(".qea", Parsers::QeaParser)
233
- rescue LoadError
234
- # QEA parser not available, skip
235
- end
236
-
237
- @default_parsers_loaded = true
238
- end
239
-
240
- # Detect format by file content (magic bytes/signatures)
241
- #
242
- # @param file_path [String] Path to the file
243
- # @return [Class, nil] Parser class based on content detection
244
- def detect_by_content(file_path) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
245
- # ensure_default_parsers_loaded!
246
- unless File.exist?(file_path)
247
- raise ArgumentError, "#{file_path} does not exist!"
248
- end
249
-
250
- # Read first few bytes to detect format
251
- File.open(file_path, "rb") do |file|
252
- header = file.read(1024) # Read first 1KB
253
-
254
- return nil if header.nil? || header.empty?
255
-
256
- # Check header match for content patterns
257
- @parsers.each do |ext, parser_class|
258
- parser_klass = parser_class.new
259
- parser_klass.content_patterns.each do |pattern|
260
- if header.match?(pattern)
261
- return parser_for_extension(ext)
262
- end
263
- end
264
- end
265
-
266
- # Check for XML/XMI signatures
267
- if header.include?("<?xml") && header.include?("xmi:")
268
- ensure_default_parsers_loaded!
269
- return parser_for_extension(".xmi")
270
- end
271
-
272
- # Check for SQLite database signature (QEA files)
273
- if header[0..15] == "SQLite format 3\0"
274
- ensure_default_parsers_loaded!
275
- return parser_for_extension(".qea")
276
- end
277
- end
278
-
279
- nil
280
- end
281
-
282
- # Get the best parser for a file using multiple detection methods
283
- #
284
- # @param file_path [String] Path to the file
285
- # @param use_content_detection [Boolean] Whether to use content detection
286
- # @return [Class, nil] Best parser class for the file
287
- def best_parser_for_file(file_path, use_content_detection: true)
288
- # First try extension-based detection
289
- parser = parser_for_file(file_path)
290
- return parser if parser
291
-
292
- # Fall back to content detection if enabled
293
- if use_content_detection
294
- parser = detect_by_content(file_path)
295
- return parser if parser
296
- end
297
-
298
- nil
299
- end
300
-
301
- private
302
-
303
- # Ensure default parsers are loaded
304
- #
305
- # @return [void]
306
- def ensure_default_parsers_loaded!
307
- load_default_parsers unless @default_parsers_loaded
308
- end
309
-
310
- # Normalize file extension to lowercase with leading dot
311
- #
312
- # @param extension [String] File extension
313
- # @return [String] Normalized extension
314
- def normalize_extension(extension)
315
- ext = extension.to_s.downcase
316
- ext = ".#{ext}" unless ext.start_with?(".")
317
- ext
318
- end
319
-
320
- # Validate file extension format
321
- #
322
- # @param extension [String] Extension to validate
323
- # @raise [ArgumentError] if extension is invalid
324
- def validate_extension!(extension)
325
- if extension.nil? || extension.empty?
326
- raise ArgumentError, "Extension cannot be nil or empty"
327
- end
328
-
329
- normalized = normalize_extension(extension)
330
- unless normalized.match?(/^\.[a-z0-9]+(.[a-z0-9]+)?/)
331
- raise ArgumentError, "Invalid extension format: #{extension}"
332
- end
333
- end
334
-
335
- # Validate parser class implements required interface
336
- #
337
- # @param parser_class [Class] Parser class to validate
338
- # @raise [ArgumentError] if parser class is invalid
339
- def validate_parser_class!(parser_class) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
340
- # Check if nil
341
- if parser_class.nil?
342
- raise ArgumentError, "Parser class cannot be nil"
343
- end
344
-
345
- # Check if it's a class
346
- unless parser_class.is_a?(Class)
347
- raise ArgumentError, "Parser must be a class"
348
- end
349
-
350
- unless parser_class < Parsers::BaseParser
351
- raise ArgumentError,
352
- "Parser class must inherit from BaseParser"
353
- end
354
- end
355
-
356
- # Convert string class name to actual class constant
357
- #
358
- # @param class_name [String] Fully qualified class name
359
- # @return [Class] The class constant
360
- # @raise [NameError] if class cannot be found
361
- def constantize_parser_class(class_name)
362
- Transformations.constantize(class_name) ||
363
- raise(NameError, "Cannot find class: #{class_name}")
364
- end end
365
- end
366
- end