ea 0.1.0 → 0.1.4

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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +125 -0
  3. data/Rakefile +12 -4
  4. data/TODO.next/00-publish-blocking-bugs.md +74 -0
  5. data/TODO.next/01-standalone-ea-gem-identity.md +76 -0
  6. data/TODO.next/02-optional-lutaml-uml-dependency.md +47 -0
  7. data/TODO.next/03-slim-lutaml-uml.md +79 -0
  8. data/TODO.next/04-loader-registry-for-uml-repository.md +49 -0
  9. data/TODO.next/05-extract-shared-transformer-methods.md +14 -0
  10. data/TODO.next/06-deduplicate-stereotype-loading.md +17 -0
  11. data/TODO.next/07-transformer-registry-in-factory.md +20 -0
  12. data/TODO.next/08-connector-type-registry.md +27 -0
  13. data/TODO.next/09-element-renderer-registry.md +29 -0
  14. data/TODO.next/10-connector-renderer-lsp.md +18 -0
  15. data/TODO.next/11-consolidate-style-knowledge.md +33 -0
  16. data/TODO.next/12-data-driven-from-db-row.md +24 -0
  17. data/TODO.next/13-extract-duplicated-methods.md +17 -0
  18. data/TODO.next/14-remove-dead-code.md +10 -0
  19. data/TODO.next/15-narrow-exception-handling.md +39 -0
  20. data/TODO.next/16-repository-indexes.md +28 -0
  21. data/TODO.next/17-fix-spec-quality-and-coverage.md +32 -0
  22. data/TODO.next/18-xmi-tool-specific-parser-architecture.md +172 -0
  23. data/TODO.next/19-fix-ea-gemspec-dependency-declarations.md +56 -0
  24. data/TODO.next/20-ci-requires-unreleased-lutaml-uml.md +63 -0
  25. data/TODO.next/21-qeatoxmi-via-xmi-gem.md +340 -0
  26. data/TODO.next/22-strip-respond-to-from-qeatoxmi-specs.md +32 -0
  27. data/TODO.next/23-cleanup-idallocator.md +41 -0
  28. data/TODO.next/24-tighten-parity-specs.md +42 -0
  29. data/TODO.next/25-sparx-eaid-format-for-synthesized-ids.md +62 -0
  30. data/TODO.next/26-fix-uppervalue-lowervalue-count-gap.md +51 -0
  31. data/TODO.next/27-extract-cardinality-module.md +68 -0
  32. data/TODO.next/28-extract-xml-sanitizer.md +51 -0
  33. data/TODO.next/29-ocp-registry-for-classifier-builders.md +58 -0
  34. data/TODO.next/30-struct-return-for-association-end.md +37 -0
  35. data/TODO.next/31-idallocator-specs.md +27 -0
  36. data/TODO.next/32-phase2-gap-sentinel-specs.md +53 -0
  37. data/TODO.next/33-normalize-lower-cleanup.md +30 -0
  38. data/TODO.next/34-document-member-end-order-rt-prefix.md +29 -0
  39. data/TODO.next/35-walk-runstate-for-instance-slots.md +76 -0
  40. data/TODO.next/36-wire-interface-realization.md +50 -0
  41. data/TODO.next/37-visibility-returns-real-booleans.md +36 -0
  42. data/config/diagram_styles.yml +200 -0
  43. data/config/model_transformations.yml +266 -0
  44. data/config/qea_schema.yml +1024 -0
  45. data/docs/ea_to_uml_type_mapping.md +89 -0
  46. data/docs/xmi_qea_conversion_capabilities.md +99 -0
  47. data/examples/lur/20251010_current_plateau_v5.1.lur +0 -0
  48. data/examples/lur/basic.lur +0 -0
  49. data/examples/lur/test-output.lur +0 -0
  50. data/examples/lur/test.lur +0 -0
  51. data/examples/lur_basic_usage.rb +221 -0
  52. data/examples/lur_cli_workflow.rb +263 -0
  53. data/examples/lur_statistics.rb +326 -0
  54. data/examples/qea/20251010_current_plateau_v5.1.qea +0 -0
  55. data/examples/qea/ArcGISWorkspace_template.qea +0 -0
  56. data/examples/qea/README_qea_parser.adoc +230 -0
  57. data/examples/qea/UmlModel_template.qea +0 -0
  58. data/examples/qea/basic.qea +0 -0
  59. data/examples/qea/simple.qea +0 -0
  60. data/examples/qea/simple_example.qea +0 -0
  61. data/examples/qea/test.qea +0 -0
  62. data/examples/qea_standalone_query.rb +73 -0
  63. data/examples/qea_to_repository.rb +51 -0
  64. data/examples/smoke_test_real_qea.rb +81 -0
  65. data/exe/ea +7 -0
  66. data/lib/ea/cli/app.rb +72 -0
  67. data/lib/ea/cli/command/base.rb +80 -0
  68. data/lib/ea/cli/command/convert.rb +62 -0
  69. data/lib/ea/cli/command/diagrams.rb +81 -0
  70. data/lib/ea/cli/command/list.rb +61 -0
  71. data/lib/ea/cli/command/parse.rb +29 -0
  72. data/lib/ea/cli/command/stats.rb +20 -0
  73. data/lib/ea/cli/command/validate.rb +41 -0
  74. data/lib/ea/cli/command.rb +15 -0
  75. data/lib/ea/cli/error.rb +34 -0
  76. data/lib/ea/cli/output/formatter.rb +34 -0
  77. data/lib/ea/cli/output/json_formatter.rb +20 -0
  78. data/lib/ea/cli/output/table_formatter.rb +42 -0
  79. data/lib/ea/cli/output/yaml_formatter.rb +20 -0
  80. data/lib/ea/cli/output.rb +56 -0
  81. data/lib/ea/cli.rb +17 -0
  82. data/lib/ea/diagram/configuration.rb +379 -0
  83. data/lib/ea/diagram/element_renderers/base_renderer.rb +77 -0
  84. data/lib/ea/diagram/element_renderers/class_renderer.rb +323 -0
  85. data/lib/ea/diagram/element_renderers/connector_renderer.rb +41 -0
  86. data/lib/ea/diagram/element_renderers/package_renderer.rb +61 -0
  87. data/lib/ea/diagram/element_renderers.rb +43 -0
  88. data/lib/ea/diagram/extractor.rb +560 -0
  89. data/lib/ea/diagram/layout_engine.rb +170 -0
  90. data/lib/ea/diagram/path_builder.rb +202 -0
  91. data/lib/ea/diagram/style_parser.rb +42 -0
  92. data/lib/ea/diagram/style_resolver.rb +276 -0
  93. data/lib/ea/diagram/svg_renderer.rb +274 -0
  94. data/lib/ea/diagram/util.rb +73 -0
  95. data/lib/ea/diagram.rb +47 -0
  96. data/lib/ea/qea/benchmark.rb +210 -0
  97. data/lib/ea/qea/database.rb +308 -0
  98. data/lib/ea/qea/factory/association_builder.rb +203 -0
  99. data/lib/ea/qea/factory/association_transformer.rb +91 -0
  100. data/lib/ea/qea/factory/attribute_tag_transformer.rb +57 -0
  101. data/lib/ea/qea/factory/attribute_transformer.rb +93 -0
  102. data/lib/ea/qea/factory/base_transformer.rb +177 -0
  103. data/lib/ea/qea/factory/class_transformer.rb +116 -0
  104. data/lib/ea/qea/factory/constraint_transformer.rb +75 -0
  105. data/lib/ea/qea/factory/data_type_transformer.rb +77 -0
  106. data/lib/ea/qea/factory/diagram_transformer.rb +157 -0
  107. data/lib/ea/qea/factory/document_builder.rb +283 -0
  108. data/lib/ea/qea/factory/ea_to_uml_factory.rb +229 -0
  109. data/lib/ea/qea/factory/enum_transformer.rb +74 -0
  110. data/lib/ea/qea/factory/generalization_builder.rb +227 -0
  111. data/lib/ea/qea/factory/generalization_transformer.rb +98 -0
  112. data/lib/ea/qea/factory/instance_transformer.rb +68 -0
  113. data/lib/ea/qea/factory/object_property_transformer.rb +58 -0
  114. data/lib/ea/qea/factory/operation_transformer.rb +66 -0
  115. data/lib/ea/qea/factory/package_transformer.rb +145 -0
  116. data/lib/ea/qea/factory/reference_resolver.rb +99 -0
  117. data/lib/ea/qea/factory/stereotype_loader.rb +39 -0
  118. data/lib/ea/qea/factory/tagged_value_transformer.rb +38 -0
  119. data/lib/ea/qea/factory/transformer_registry.rb +80 -0
  120. data/lib/ea/qea/factory.rb +37 -0
  121. data/lib/ea/qea/file_detector.rb +178 -0
  122. data/lib/ea/qea/infrastructure/database_connection.rb +100 -0
  123. data/lib/ea/qea/infrastructure/schema_reader.rb +136 -0
  124. data/lib/ea/qea/infrastructure/table_reader.rb +224 -0
  125. data/lib/ea/qea/infrastructure.rb +12 -0
  126. data/lib/ea/qea/models/base_model.rb +59 -0
  127. data/lib/ea/qea/models/ea_attribute.rb +109 -0
  128. data/lib/ea/qea/models/ea_attribute_tag.rb +100 -0
  129. data/lib/ea/qea/models/ea_complexity_type.rb +79 -0
  130. data/lib/ea/qea/models/ea_connector.rb +160 -0
  131. data/lib/ea/qea/models/ea_connector_type.rb +60 -0
  132. data/lib/ea/qea/models/ea_constraint_type.rb +63 -0
  133. data/lib/ea/qea/models/ea_datatype.rb +104 -0
  134. data/lib/ea/qea/models/ea_diagram.rb +115 -0
  135. data/lib/ea/qea/models/ea_diagram_link.rb +78 -0
  136. data/lib/ea/qea/models/ea_diagram_object.rb +73 -0
  137. data/lib/ea/qea/models/ea_diagram_type.rb +56 -0
  138. data/lib/ea/qea/models/ea_document.rb +63 -0
  139. data/lib/ea/qea/models/ea_object.rb +223 -0
  140. data/lib/ea/qea/models/ea_object_constraint.rb +53 -0
  141. data/lib/ea/qea/models/ea_object_property.rb +87 -0
  142. data/lib/ea/qea/models/ea_object_type.rb +73 -0
  143. data/lib/ea/qea/models/ea_operation.rb +127 -0
  144. data/lib/ea/qea/models/ea_operation_param.rb +76 -0
  145. data/lib/ea/qea/models/ea_package.rb +78 -0
  146. data/lib/ea/qea/models/ea_script.rb +62 -0
  147. data/lib/ea/qea/models/ea_status_type.rb +66 -0
  148. data/lib/ea/qea/models/ea_stereotype.rb +57 -0
  149. data/lib/ea/qea/models/ea_tagged_value.rb +99 -0
  150. data/lib/ea/qea/models/ea_xref.rb +165 -0
  151. data/lib/ea/qea/models.rb +35 -0
  152. data/lib/ea/qea/repositories/base_repository.rb +225 -0
  153. data/lib/ea/qea/repositories/object_repository.rb +219 -0
  154. data/lib/ea/qea/repositories.rb +10 -0
  155. data/lib/ea/qea/services/configuration.rb +211 -0
  156. data/lib/ea/qea/services/database_loader.rb +191 -0
  157. data/lib/ea/qea/services.rb +10 -0
  158. data/lib/ea/qea/validation/association_validator.rb +73 -0
  159. data/lib/ea/qea/validation/attribute_validator.rb +91 -0
  160. data/lib/ea/qea/validation/base_validator.rb +331 -0
  161. data/lib/ea/qea/validation/class_validator.rb +121 -0
  162. data/lib/ea/qea/validation/database/circular_reference_validator.rb +109 -0
  163. data/lib/ea/qea/validation/database/orphan_validator.rb +153 -0
  164. data/lib/ea/qea/validation/database/referential_integrity_validator.rb +128 -0
  165. data/lib/ea/qea/validation/database.rb +16 -0
  166. data/lib/ea/qea/validation/diagram_validator.rb +112 -0
  167. data/lib/ea/qea/validation/formatters/json_formatter.rb +137 -0
  168. data/lib/ea/qea/validation/formatters/text_formatter.rb +235 -0
  169. data/lib/ea/qea/validation/formatters.rb +12 -0
  170. data/lib/ea/qea/validation/operation_validator.rb +71 -0
  171. data/lib/ea/qea/validation/package_validator.rb +111 -0
  172. data/lib/ea/qea/validation/validation_engine.rb +387 -0
  173. data/lib/ea/qea/validation/validation_message.rb +144 -0
  174. data/lib/ea/qea/validation/validation_result.rb +210 -0
  175. data/lib/ea/qea/validation/validator_registry.rb +134 -0
  176. data/lib/ea/qea/validation.rb +28 -0
  177. data/lib/ea/qea/verification/comparison_result.rb +264 -0
  178. data/lib/ea/qea/verification/document_normalizer.rb +169 -0
  179. data/lib/ea/qea/verification/document_verifier.rb +322 -0
  180. data/lib/ea/qea/verification/element_comparator.rb +277 -0
  181. data/lib/ea/qea/verification/structure_matcher.rb +287 -0
  182. data/lib/ea/qea/verification.rb +14 -0
  183. data/lib/ea/qea.rb +185 -0
  184. data/lib/ea/transformations/configuration.rb +333 -0
  185. data/lib/ea/transformations/format_registry.rb +366 -0
  186. data/lib/ea/transformations/parsers/base_parser.rb +482 -0
  187. data/lib/ea/transformations/parsers/qea_parser.rb +401 -0
  188. data/lib/ea/transformations/parsers/xmi_parser.rb +243 -0
  189. data/lib/ea/transformations/transformation_engine.rb +390 -0
  190. data/lib/ea/transformations.rb +85 -0
  191. data/lib/ea/transformers/qea_to_xmi/association_end.rb +19 -0
  192. data/lib/ea/transformers/qea_to_xmi/cardinality.rb +96 -0
  193. data/lib/ea/transformers/qea_to_xmi/context.rb +106 -0
  194. data/lib/ea/transformers/qea_to_xmi/guid_format.rb +56 -0
  195. data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +92 -0
  196. data/lib/ea/transformers/qea_to_xmi/run_state.rb +107 -0
  197. data/lib/ea/transformers/qea_to_xmi/transformer.rb +607 -0
  198. data/lib/ea/transformers/qea_to_xmi/visibility.rb +73 -0
  199. data/lib/ea/transformers/qea_to_xmi.rb +29 -0
  200. data/lib/ea/transformers/uml_to_xmi/id_generator.rb +54 -0
  201. data/lib/ea/transformers/uml_to_xmi/transformer.rb +152 -0
  202. data/lib/ea/transformers/uml_to_xmi/writer.rb +96 -0
  203. data/lib/ea/transformers/uml_to_xmi.rb +16 -0
  204. data/lib/ea/transformers.rb +34 -0
  205. data/lib/ea/version.rb +1 -1
  206. data/lib/ea/xmi/liquid_drops/association_drop.rb +56 -0
  207. data/lib/ea/xmi/liquid_drops/attribute_drop.rb +72 -0
  208. data/lib/ea/xmi/liquid_drops/cardinality_drop.rb +35 -0
  209. data/lib/ea/xmi/liquid_drops/connector_drop.rb +54 -0
  210. data/lib/ea/xmi/liquid_drops/constraint_drop.rb +29 -0
  211. data/lib/ea/xmi/liquid_drops/data_type_drop.rb +63 -0
  212. data/lib/ea/xmi/liquid_drops/dependency_drop.rb +36 -0
  213. data/lib/ea/xmi/liquid_drops/diagram_drop.rb +34 -0
  214. data/lib/ea/xmi/liquid_drops/enum_drop.rb +49 -0
  215. data/lib/ea/xmi/liquid_drops/enum_owned_literal_drop.rb +25 -0
  216. data/lib/ea/xmi/liquid_drops/generalization_attribute_drop.rb +87 -0
  217. data/lib/ea/xmi/liquid_drops/generalization_drop.rb +127 -0
  218. data/lib/ea/xmi/liquid_drops/klass_drop.rb +191 -0
  219. data/lib/ea/xmi/liquid_drops/operation_drop.rb +29 -0
  220. data/lib/ea/xmi/liquid_drops/package_drop.rb +108 -0
  221. data/lib/ea/xmi/liquid_drops/root_drop.rb +34 -0
  222. data/lib/ea/xmi/liquid_drops/source_target_drop.rb +43 -0
  223. data/lib/ea/xmi/lookup_service.rb +89 -0
  224. data/lib/ea/xmi/parser.rb +919 -0
  225. data/lib/ea/xmi.rb +35 -0
  226. data/lib/ea.rb +10 -1
  227. metadata +382 -9
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module Ea
6
+ module Cli
7
+ module Output
8
+ class YamlFormatter < Formatter
9
+ def render(rows, columns: [])
10
+ data = rows.map do |row|
11
+ normalize_row(row, columns)
12
+ end
13
+ puts data.to_yaml
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ Ea::Cli::Output.register(:yaml, Ea::Cli::Output::YamlFormatter)
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Cli
5
+ # Output formatters for the CLI. Each formatter self-registers via
6
+ # `Ea::Cli::Output.register(:name, Class)` at file-load time. Lookup via
7
+ # `Ea::Cli::Output.for(:name)` triggers autoloads on first access.
8
+ module Output
9
+ autoload :Formatter, "ea/cli/output/formatter"
10
+ autoload :TableFormatter, "ea/cli/output/table_formatter"
11
+ autoload :JsonFormatter, "ea/cli/output/json_formatter"
12
+ autoload :YamlFormatter, "ea/cli/output/yaml_formatter"
13
+
14
+ @formatters = {}
15
+ @formatters_loaded = false
16
+
17
+ class << self
18
+ def register(name, klass)
19
+ @formatters[name.to_sym] = klass
20
+ end
21
+
22
+ def for(name)
23
+ ensure_formatters_loaded
24
+ klass = @formatters[name.to_sym]
25
+ klass ||
26
+ raise(ArgumentError,
27
+ "No output formatter '#{name}'. " \
28
+ "Registered: #{@formatters.keys.join(', ')}")
29
+ end
30
+
31
+ # Convenience: returns a fresh formatter instance for `name`.
32
+ def instance_for(name)
33
+ self.for(name).new
34
+ end
35
+
36
+ def registered_formats
37
+ ensure_formatters_loaded
38
+ @formatters.keys
39
+ end
40
+
41
+ private
42
+
43
+ # Trigger autoload of each known formatter so its self-register
44
+ # block runs. Referencing the constant is enough.
45
+ def ensure_formatters_loaded
46
+ return if @formatters_loaded
47
+
48
+ TableFormatter
49
+ JsonFormatter
50
+ YamlFormatter
51
+ @formatters_loaded = true
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
data/lib/ea/cli.rb ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Cli
5
+ autoload :App, "ea/cli/app"
6
+ autoload :Command, "ea/cli/command"
7
+ autoload :Output, "ea/cli/output"
8
+
9
+ # All error classes live in ea/cli/error.rb — declare each so any
10
+ # reference (not just Ea::Cli::Error) triggers the autoload.
11
+ autoload :Error, "ea/cli/error"
12
+ autoload :FileNotFound, "ea/cli/error"
13
+ autoload :UnsupportedFormat, "ea/cli/error"
14
+ autoload :MissingUmlDependency, "ea/cli/error"
15
+ autoload :UnknownAction, "ea/cli/error"
16
+ end
17
+ end
@@ -0,0 +1,379 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module Ea
6
+ module Diagram
7
+ # Configuration management for diagram styling
8
+ #
9
+ # This class provides hierarchical style resolution with the
10
+ # following priority:
11
+ # 1. EA Data (QEA/XMI) - Highest priority
12
+ # 2. Class-specific overrides (user config)
13
+ # 3. Package-based styling (wildcard support)
14
+ # 4. Stereotype-based styling
15
+ # 5. Global defaults - Lowest priority
16
+ #
17
+ # @example
18
+ # config = Configuration.new("config/diagram_styles.yml")
19
+ # fill_color = config.style_for(element, "colors.fill")
20
+ # font_family = config.style_for(element, "fonts.class_name.family")
21
+ class Configuration
22
+ attr_reader :config_data
23
+
24
+ # Default configuration file paths in order of preference
25
+ DEFAULT_CONFIG_PATHS = [
26
+ "config/diagram_styles.yml",
27
+ File.expand_path("~/.lutaml/diagram_styles.yml"),
28
+ ].freeze
29
+
30
+ # Initialize configuration with optional custom path
31
+ #
32
+ # @param config_path [String, nil] Path to custom configuration file
33
+ def initialize(config_path = nil)
34
+ @config_data = load_configuration(config_path)
35
+ end
36
+
37
+ # Get style for a specific element
38
+ #
39
+ # @param element [Lutaml::Uml::TopElement, Object] UML element
40
+ # @param property [String] Style property path (e.g., "colors.fill")
41
+ # @return [Object] Style value
42
+ def style_for(element, property) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
43
+ return nil if property.nil? || property.empty?
44
+
45
+ # Priority: Class-specific > Package > Stereotype > Defaults
46
+ value = nil
47
+
48
+ # 1. Try class-specific override (highest priority)
49
+ if element.is_a?(Lutaml::Uml::TopElement) && element.name
50
+ class_config = dig_config("classes.#{element.name}.#{property}")
51
+ value = class_config if class_config
52
+ end
53
+
54
+ # 2. Try package-based styling
55
+ if !value &&
56
+ element.is_a?(Lutaml::Uml::Diagram) && element.package_name
57
+ # Support wildcards: "CityGML::*"
58
+ package_configs = config_data["packages"] || {}
59
+ package_configs.each do |pattern, pkg_config|
60
+ if matches_package?(element.package_name, pattern)
61
+ pkg_value = dig_hash(pkg_config, property)
62
+ value = pkg_value if pkg_value
63
+ break
64
+ end
65
+ end
66
+ end
67
+
68
+ # 3. Try stereotype-based styling
69
+ if !value && element.is_a?(Lutaml::Uml::TopElement) && element.stereotype
70
+ stereotypes = Array(element.stereotype)
71
+ stereotypes.each do |stereo|
72
+ stereo_value = dig_config("stereotypes.#{stereo}.#{property}")
73
+ if stereo_value
74
+ value = stereo_value
75
+ break
76
+ end
77
+ end
78
+ end
79
+
80
+ # 4. Fall back to defaults (lowest priority)
81
+ # Handle property name aliases
82
+ # (e.g., "colors.fill" -> "colors.default_fill")
83
+ unless value
84
+ value = dig_config("defaults.#{property}")
85
+
86
+ # If not found, try with "default_" prefix for certain properties
87
+ unless value
88
+ if property.start_with?("colors.fill")
89
+ value = dig_config("defaults.colors.default_fill")
90
+ elsif property.start_with?("colors.stroke") &&
91
+ !property.include?("stroke_")
92
+ value = dig_config("defaults.colors.default_stroke")
93
+ end
94
+ end
95
+ end
96
+
97
+ value
98
+ end
99
+
100
+ # Get connector style
101
+ #
102
+ # @param connector_type [String]
103
+ # Type of connector (generalization, association, etc.)
104
+ # @param property [String] Style property path
105
+ # @return [Object] Style value
106
+ def connector_style(connector_type, property)
107
+ dig_config("connectors.#{connector_type}.#{property}")
108
+ end
109
+
110
+ # Get legend configuration
111
+ #
112
+ # @return [Hash] Legend configuration
113
+ def legend_config
114
+ config_data["legend"] || {}
115
+ end
116
+
117
+ # Get the entire configuration data
118
+ #
119
+ # @return [Hash] Complete configuration data
120
+ def to_h
121
+ config_data
122
+ end
123
+
124
+ # Navigate hash using dot notation
125
+ #
126
+ # @param hash [Hash] Hash to navigate
127
+ # @param path [String] Path with dot notation
128
+ # @return [Object] Value at path
129
+ def dig_hash(hash, path) # rubocop:disable Metrics/CyclomaticComplexity
130
+ return nil if path.nil? || path.empty?
131
+ return nil unless hash.is_a?(Hash)
132
+
133
+ keys = path.split(".")
134
+ return nil if keys.empty?
135
+
136
+ keys.reduce(hash) do |h, key|
137
+ return nil unless h.is_a?(Hash)
138
+
139
+ h[key]
140
+ end
141
+ end
142
+
143
+ # Check if package name matches pattern (supports wildcards)
144
+ #
145
+ # @param package_name [String] Package name to test
146
+ # @param pattern [String] Pattern with wildcards (e.g., "CityGML::*")
147
+ # @return [Boolean] True if matches
148
+ def matches_package?(package_name, pattern)
149
+ return false unless package_name && pattern
150
+
151
+ # Support wildcards: "CityGML::*" matches "CityGML::Core"
152
+ regex_pattern = pattern.gsub("*", ".*")
153
+ Regexp.new("^#{regex_pattern}$").match?(package_name)
154
+ rescue RegexpError => e
155
+ warn "Warning: Invalid package pattern '#{pattern}': #{e.message}"
156
+ false
157
+ end
158
+
159
+ # Deep merge two hashes
160
+ #
161
+ # @param hash1 [Hash] Base hash
162
+ # @param hash2 [Hash] Hash to merge in
163
+ # @return [Hash] Merged hash
164
+ def deep_merge(hash1, hash2)
165
+ hash1.merge(hash2) do |_key, old_val, new_val|
166
+ if old_val.is_a?(Hash) && new_val.is_a?(Hash)
167
+ deep_merge(old_val, new_val)
168
+ else
169
+ new_val
170
+ end
171
+ end
172
+ end
173
+
174
+ private
175
+
176
+ # Load configuration from files
177
+ #
178
+ # @param custom_path [String, nil] Custom configuration path
179
+ # @return [Hash] Merged configuration data
180
+ def load_configuration(custom_path = nil) # rubocop:disable Metrics/MethodLength
181
+ paths = custom_path ? [custom_path] : DEFAULT_CONFIG_PATHS
182
+ merged_config = default_config
183
+
184
+ paths.each do |path|
185
+ next unless File.exist?(path)
186
+
187
+ begin
188
+ user_config = YAML.load_file(path)
189
+ if user_config.is_a?(Hash)
190
+ merged_config = deep_merge(merged_config, user_config)
191
+ end
192
+ rescue Psych::SyntaxError, Errno::ENOENT, Errno::EACCES,
193
+ IOError => e
194
+ warn "Warning: Failed to load configuration from " \
195
+ "#{path}: #{e.message}"
196
+ end
197
+ end
198
+
199
+ merged_config
200
+ end
201
+
202
+ # Built-in default configuration (minimal, as fallback)
203
+ #
204
+ # @return [Hash] Default configuration data
205
+ def default_config # rubocop:disable Metrics/MethodLength
206
+ {
207
+ "defaults" => {
208
+ "colors" => {
209
+ "background" => "#FFFFFF",
210
+ "default_fill" => "#E0E0E0",
211
+ "default_stroke" => "#000000",
212
+ "text" => "#000000",
213
+ },
214
+ "fonts" => {
215
+ "default" => {
216
+ "family" => "Carlito, Arial, sans-serif",
217
+ "size" => 9,
218
+ "weight" => 400,
219
+ "style" => "normal",
220
+ },
221
+ "class_name" => {
222
+ "family" => "Carlito, Arial, sans-serif",
223
+ "size" => 9,
224
+ "weight" => 700,
225
+ "style" => "normal",
226
+ },
227
+ "stereotype" => {
228
+ "family" => "Carlito, Arial, sans-serif",
229
+ "size" => 9,
230
+ "weight" => 400,
231
+ "style" => "normal",
232
+ },
233
+ },
234
+ "box" => {
235
+ "stroke_width" => 2,
236
+ "stroke_linecap" => "round",
237
+ "stroke_linejoin" => "bevel",
238
+ "corner_radius" => 0,
239
+ "padding" => 5,
240
+ },
241
+ "text" => {
242
+ "visibility_public" => "+",
243
+ "visibility_private" => "-",
244
+ "visibility_protected" => "#",
245
+ "visibility_package" => "~",
246
+ "cardinality_format" => "[%s]",
247
+ "stereotype_format" => "«%s»",
248
+ },
249
+ },
250
+ "stereotypes" => {
251
+ "DataType" => {
252
+ "colors" => {
253
+ "fill" => "#FFCCFF",
254
+ "stroke" => "#000000",
255
+ },
256
+ "fonts" => {
257
+ "class_name" => {
258
+ "weight" => 700,
259
+ "style" => "italic",
260
+ },
261
+ },
262
+ },
263
+ "FeatureType" => {
264
+ "colors" => {
265
+ "fill" => "#FFFFCC",
266
+ "stroke" => "#000000",
267
+ },
268
+ },
269
+ "GMLType" => {
270
+ "colors" => {
271
+ "fill" => "#CCFFCC",
272
+ "stroke" => "#000000",
273
+ },
274
+ },
275
+ "Interface" => {
276
+ "colors" => {
277
+ "fill" => "#FFFFEE",
278
+ "stroke" => "#000000",
279
+ },
280
+ "fonts" => {
281
+ "class_name" => {
282
+ "style" => "italic",
283
+ },
284
+ },
285
+ },
286
+ },
287
+ "connectors" => {
288
+ "generalization" => {
289
+ "arrow" => {
290
+ "type" => "hollow_triangle",
291
+ "size" => 10,
292
+ },
293
+ "line" => {
294
+ "stroke_width" => 1,
295
+ "stroke" => "#000000",
296
+ },
297
+ },
298
+ "association" => {
299
+ "arrow" => {
300
+ "type" => "open_arrow",
301
+ "size" => 8,
302
+ },
303
+ "line" => {
304
+ "stroke_width" => 1,
305
+ "stroke" => "#000000",
306
+ },
307
+ "labels" => {
308
+ "show_role_names" => true,
309
+ "show_cardinality" => true,
310
+ "font" => {
311
+ "family" => "Carlito, Arial, sans-serif",
312
+ "size" => 8,
313
+ },
314
+ },
315
+ },
316
+ "dependency" => {
317
+ "arrow" => {
318
+ "type" => "open_arrow",
319
+ "size" => 8,
320
+ },
321
+ "line" => {
322
+ "stroke_width" => 1,
323
+ "stroke" => "#000000",
324
+ "stroke_dasharray" => "5,5",
325
+ },
326
+ },
327
+ "aggregation" => {
328
+ "arrow" => {
329
+ "type" => "diamond",
330
+ "size" => 10,
331
+ },
332
+ "line" => {
333
+ "stroke_width" => 1,
334
+ "stroke" => "#000000",
335
+ },
336
+ },
337
+ "composition" => {
338
+ "arrow" => {
339
+ "type" => "filled_diamond",
340
+ "size" => 10,
341
+ },
342
+ "line" => {
343
+ "stroke_width" => 1,
344
+ "stroke" => "#000000",
345
+ },
346
+ },
347
+ },
348
+ "legend" => {
349
+ "enabled" => true,
350
+ "position" => "bottom_right",
351
+ "title" => "Legend",
352
+ "entries" => [
353
+ {
354
+ "label" => "i-UR DataTypes",
355
+ "color" => "#FFCCFF",
356
+ },
357
+ {
358
+ "label" => "CityGML FeatureTypes",
359
+ "color" => "#FFFFCC",
360
+ },
361
+ {
362
+ "label" => "GML Types",
363
+ "color" => "#CCFFCC",
364
+ },
365
+ ],
366
+ },
367
+ }
368
+ end
369
+
370
+ # Navigate configuration using dot notation
371
+ #
372
+ # @param path [String] Configuration path (e.g., "colors.fill")
373
+ # @return [Object] Configuration value
374
+ def dig_config(path)
375
+ dig_hash(config_data, path)
376
+ end
377
+ end
378
+ end
379
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Diagram
5
+ module ElementRenderers
6
+ # Base renderer for diagram elements
7
+ class BaseRenderer
8
+ include Ea::Diagram::Util
9
+
10
+ attr_reader :element, :style_resolver
11
+
12
+ def initialize(element, style_resolver)
13
+ @element = element
14
+ @style_resolver = style_resolver
15
+ end
16
+
17
+ # Render the element as SVG
18
+ # @return [String] SVG content for the element
19
+ def render
20
+ style = style_resolver.resolve_element_style(element)
21
+
22
+ <<~SVG
23
+ <g class="lutaml-diagram-element lutaml-diagram-#{element[:type]}"
24
+ data-element-id="#{element[:id]}"
25
+ data-element-type="#{element[:type]}">
26
+ #{render_shape(style)}
27
+ #{render_label(style)}
28
+ </g>
29
+ SVG
30
+ end
31
+
32
+ def render_shape(_style)
33
+ ""
34
+ end
35
+
36
+ def render_label(style) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
37
+ return "" unless element[:name]
38
+
39
+ x = element[:x] || 0
40
+ y = element[:y] || 0
41
+ width = element[:width] || 120
42
+ height = element[:height] || 80
43
+
44
+ # Center the label
45
+ text_x = x + (width / 2)
46
+ # Slight vertical offset for better centering
47
+ text_y = y + (height / 2) + 5
48
+
49
+ <<~SVG
50
+ <text x="#{text_x}"
51
+ y="#{text_y}"
52
+ text-anchor="middle"
53
+ dominant-baseline="middle"
54
+ font-family="#{style[:font_family]}"
55
+ font-size="#{style[:font_size]}"
56
+ font-weight="#{style[:font_weight] || 'normal'}"
57
+ fill="#{style[:text_color] || '#000000'}"
58
+ class="lutaml-diagram-label">
59
+ #{escape_text(element[:name])}
60
+ </text>
61
+ SVG
62
+ end
63
+
64
+ def escape_text(text)
65
+ return "" unless text
66
+
67
+ text.to_s
68
+ .gsub("&", "&amp;")
69
+ .gsub("<", "&lt;")
70
+ .gsub(">", "&gt;")
71
+ .gsub('"', "&quot;")
72
+ .gsub("'", "&apos;")
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end