ea 0.5.1 → 0.6.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 (145) hide show
  1. checksums.yaml +4 -4
  2. data/TODO.complete/01-t-xref-parsing.md +1 -1
  3. data/TODO.complete/02-per-label-geometry.md +1 -1
  4. data/TODO.complete/03-object-properties-constraints.md +1 -1
  5. data/TODO.complete/04-operation-params.md +1 -1
  6. data/TODO.complete/05-pdata-flag-matrix.md +1 -1
  7. data/TODO.complete/06-emf-image-rendering.md +1 -1
  8. data/TODO.complete/07-auxiliary-tables.md +1 -1
  9. data/TODO.complete/10-mdg-stereotype-registry.md +1 -1
  10. data/TODO.complete/11-gml-xsd-generation.md +1 -1
  11. data/TODO.complete/13-shapescript-interpreter.md +12 -23
  12. data/TODO.complete/20-cli-diff.md +1 -1
  13. data/TODO.complete/21-cli-render.md +1 -1
  14. data/TODO.complete/22-cli-export.md +1 -1
  15. data/TODO.complete/23-cli-mdg.md +1 -1
  16. data/TODO.complete/30-headerlines-ocp-refactor.md +1 -1
  17. data/TODO.complete/32-spec-coverage.md +1 -1
  18. data/TODO.complete/33-blocked-parity-gaps.md +1 -1
  19. data/TODO.complete/36-stereotype-icon-wiring.md +1 -1
  20. data/TODO.complete/37-cli-lint.md +1 -1
  21. data/TODO.complete/38-cli-query.md +1 -1
  22. data/TODO.complete/39-cli-info.md +1 -1
  23. data/TODO.complete/40-ocl-evaluator.md +1 -1
  24. data/TODO.complete/41-round-trip-spec.md +1 -1
  25. data/TODO.complete/42-html-diff-report.md +1 -1
  26. data/TODO.complete/43-json-schema-export.md +1 -1
  27. data/TODO.complete/44-remaining-qea-tables.md +1 -1
  28. data/TODO.complete/45-shapescript-extensions.md +1 -1
  29. data/TODO.complete/46-xsd-consolidation.md +1 -1
  30. data/TODO.complete/47-headerlines-legacy-removal.md +1 -1
  31. data/TODO.complete/51-remove-respond-to.md +42 -15
  32. data/TODO.complete/52-xsd-decouple-fixtures.md +1 -1
  33. data/TODO.complete/54-wire-stereotype-icon-renderer.md +1 -1
  34. data/TODO.complete/55-wire-shapescript.md +1 -1
  35. data/TODO.complete/56-wire-emf-renderer.md +1 -1
  36. data/TODO.complete/57-wire-ocl-evaluator.md +1 -1
  37. data/TODO.complete/58-diff-modifications.md +1 -1
  38. data/TODO.complete/59-delete-toy-xmi-export.md +1 -1
  39. data/TODO.complete/60-curate-json-export.md +1 -1
  40. data/TODO.complete/61-plantuml-relationships.md +1 -1
  41. data/TODO.complete/62-changelog.md +1 -1
  42. data/TODO.complete/63-dependabot-fix.md +1 -1
  43. data/TODO.complete/64-qeatoxmi-connectors.md +15 -0
  44. data/TODO.complete/65-qeatoxmi-diagrams.md +15 -0
  45. data/TODO.complete/66-qeatoxmi-style-tags-docs.md +35 -0
  46. data/TODO.complete/67-ocl-collection-ops.md +20 -0
  47. data/TODO.complete/68-ocl-comparison-ops.md +24 -0
  48. data/TODO.complete/69-spec-new-models.md +20 -0
  49. data/TODO.complete/70-specs-for-all-models.md +21 -0
  50. data/TODO.complete/71-autoload-audit.md +26 -0
  51. data/TODO.complete/72-extension-serializer-ocp-naming.md +26 -0
  52. data/TODO.complete/73-ocl-validator-latent-bugs.md +50 -0
  53. data/TODO.complete/74-visibility-symbol-dry.md +39 -0
  54. data/TODO.complete/75-database-table-collection-registry.md +32 -0
  55. data/TODO.complete/76-stale-todo-cleanup.md +52 -0
  56. data/TODO.complete/77-eliminate-banned-patterns-from-specs.md +55 -0
  57. data/TODO.complete/78-mdg-stereotype-emission.md +66 -0
  58. data/TODO.complete/79-equivalence-fixes.md +57 -0
  59. data/TODO.complete/80-performance-extension-serializer-indexes.md +43 -0
  60. data/TODO.complete/81-dry-scope-guid-extraction.md +33 -0
  61. data/TODO.complete/82-public-send-audit-conclusion.md +25 -0
  62. data/TODO.complete/83-dry-shared-xml-escape-guid-format.md +52 -0
  63. data/TODO.complete/84-ocp-markers-shape-registry.md +54 -0
  64. data/TODO.complete/85-frozen-literal-rubocop-normalize-type-spec.md +38 -0
  65. data/TODO.complete/86-frozen-sweep-lint-query-specs.md +55 -0
  66. data/TODO.complete/STATUS.md +26 -5
  67. data/lib/ea/cli/app.rb +1 -0
  68. data/lib/ea/cli/command/convert.rb +1 -1
  69. data/lib/ea/cli/command/export.rb +10 -1
  70. data/lib/ea/cli/command/mdg.rb +1 -10
  71. data/lib/ea/cli/command/render.rb +2 -1
  72. data/lib/ea/diff/html_reporter.rb +1 -5
  73. data/lib/ea/export/json/generator.rb +5 -6
  74. data/lib/ea/export/plantuml/generator.rb +5 -3
  75. data/lib/ea/guid_format.rb +91 -0
  76. data/lib/ea/mdg/document.rb +2 -1
  77. data/lib/ea/mdg/loader.rb +2 -1
  78. data/lib/ea/mdg/registry.rb +57 -2
  79. data/lib/ea/mdg/xml/applies_to.rb +1 -1
  80. data/lib/ea/mdg/xml/apply.rb +1 -1
  81. data/lib/ea/mdg/xml/content.rb +1 -1
  82. data/lib/ea/mdg/xml/documentation.rb +1 -1
  83. data/lib/ea/mdg/xml/stereotype.rb +1 -1
  84. data/lib/ea/mdg/xml/stereotypes.rb +1 -1
  85. data/lib/ea/mdg/xml/tag.rb +1 -1
  86. data/lib/ea/mdg/xml/tagged_values.rb +1 -1
  87. data/lib/ea/mdg/xml/technology.rb +1 -1
  88. data/lib/ea/mdg/xml/uml_profile.rb +1 -1
  89. data/lib/ea/mdg/xml/uml_profiles.rb +1 -1
  90. data/lib/ea/mdg/xml.rb +2 -1
  91. data/lib/ea/mdg.rb +2 -1
  92. data/lib/ea/model/ghost_label.rb +2 -1
  93. data/lib/ea/ocl/evaluator.rb +39 -0
  94. data/lib/ea/ocl/nodes.rb +9 -0
  95. data/lib/ea/ocl/parser.rb +60 -6
  96. data/lib/ea/qea/database.rb +57 -2
  97. data/lib/ea/qea/factory/reference_resolver.rb +1 -1
  98. data/lib/ea/qea/models/ea_attribute.rb +1 -17
  99. data/lib/ea/qea/models/ea_connector.rb +3 -2
  100. data/lib/ea/qea/models/ea_object.rb +14 -9
  101. data/lib/ea/qea/models/ea_operation.rb +1 -17
  102. data/lib/ea/qea/models/ea_xref.rb +3 -0
  103. data/lib/ea/qea/models/scope_predicate.rb +29 -0
  104. data/lib/ea/qea/models.rb +1 -0
  105. data/lib/ea/qea/repositories/base_repository.rb +4 -1
  106. data/lib/ea/qea/validation/base_validator.rb +3 -48
  107. data/lib/ea/qea/validation/database/ocl_constraint_validator.rb +23 -13
  108. data/lib/ea/shapescript/renderer.rb +1 -3
  109. data/lib/ea/sources/qea/diagram_builder.rb +1 -0
  110. data/lib/ea/sources/qea/id_normalizer.rb +2 -2
  111. data/lib/ea/svg/ea_emitter/background.rb +2 -1
  112. data/lib/ea/svg/ea_emitter/element/attribute_line_builder.rb +1 -6
  113. data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +2 -7
  114. data/lib/ea/svg/ea_emitter/elements.rb +1 -3
  115. data/lib/ea/svg/ea_emitter/ghost_labels.rb +2 -1
  116. data/lib/ea/svg/ea_emitter/label/end_label.rb +1 -6
  117. data/lib/ea/svg/ea_emitter/marker/package_import.rb +1 -1
  118. data/lib/ea/svg/ea_emitter/markers.rb +52 -23
  119. data/lib/ea/svg/ea_emitter/text_renderer.rb +1 -4
  120. data/lib/ea/svg/ea_emitter/visibility_symbol.rb +37 -0
  121. data/lib/ea/svg/ea_emitter.rb +1 -0
  122. data/lib/ea/svg/parity/source.rb +0 -2
  123. data/lib/ea/svg/parity/suite.rb +0 -1
  124. data/lib/ea/transformers/qea_to_xmi/cardinality.rb +40 -28
  125. data/lib/ea/transformers/qea_to_xmi/context.rb +35 -1
  126. data/lib/ea/transformers/qea_to_xmi/extension_serializer.rb +686 -0
  127. data/lib/ea/transformers/qea_to_xmi/guid_format.rb +5 -48
  128. data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +55 -54
  129. data/lib/ea/transformers/qea_to_xmi/primitive_types.rb +105 -0
  130. data/lib/ea/transformers/qea_to_xmi/profile_serializer.rb +163 -0
  131. data/lib/ea/transformers/qea_to_xmi/run_state.rb +7 -6
  132. data/lib/ea/transformers/qea_to_xmi/transformer.rb +423 -76
  133. data/lib/ea/transformers/qea_to_xmi/visibility.rb +30 -19
  134. data/lib/ea/transformers/qea_to_xmi.rb +5 -2
  135. data/lib/ea/transformers.rb +8 -2
  136. data/lib/ea/validation.rb +1 -1
  137. data/lib/ea/version.rb +1 -1
  138. data/lib/ea/xmi/liquid_drops/attribute_drop.rb +12 -4
  139. data/lib/ea/xmi/liquid_drops/klass_drop.rb +1 -2
  140. data/lib/ea/xmi/lookup_service.rb +4 -0
  141. data/lib/ea/xmi/parser.rb +30 -1
  142. data/lib/ea/xml_escape.rb +21 -0
  143. data/lib/ea.rb +2 -0
  144. metadata +34 -11
  145. data/lutaml-uml-0.5.1.gem +0 -0
data/lib/ea/ocl/parser.rb CHANGED
@@ -33,7 +33,7 @@ module Ea
33
33
  end
34
34
 
35
35
  # Parse one expression. Returns a single AST node.
36
- def parse_expression(source)
36
+ def parse_expression(source) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
37
37
  s = source.strip
38
38
 
39
39
  # Strip outer parens
@@ -45,7 +45,14 @@ module Ea
45
45
  operand: parse_expression(s[4..]))
46
46
  end
47
47
 
48
- # 'and' / 'or' at top level (find the operator not inside parens)
48
+ # 'and' / 'or' / comparison ops comparison has higher
49
+ # precedence than logical, so check it first.
50
+ if (split = split_on_comparison_op(s))
51
+ op, left, right = split
52
+ return Nodes::Comparison.new(op: op,
53
+ left: parse_expression(left),
54
+ right: parse_expression(right))
55
+ end
49
56
  if (split = split_on_logical_op(s))
50
57
  op, left, right = split
51
58
  return Nodes::BinaryOp.new(op: op,
@@ -58,6 +65,16 @@ module Ea
58
65
  return collection_node(m[2].to_sym, m[1], m[3], m[4])
59
66
  end
60
67
 
68
+ # Collection ->size() — integer cardinality
69
+ if (m = s.match(/^(.+?)->size\(\)\z/))
70
+ return Nodes::CollectionSize.new(collection: parse_expression(m[1]))
71
+ end
72
+
73
+ # Collection ->isEmpty() — boolean
74
+ if (m = s.match(/^(.+?)->isEmpty\(\)\z/))
75
+ return Nodes::CollectionIsEmpty.new(collection: parse_expression(m[1]))
76
+ end
77
+
61
78
  # String.matches('regex')
62
79
  if (m = s.match(/^(.+?)\.matches\(\s*['"](.+?)['"]\s*\)\z/))
63
80
  return Nodes::StringMatches.new(string: parse_expression(m[1]),
@@ -99,7 +116,6 @@ module Ea
99
116
  # Find the topmost ` and ` or ` or ` outside of parens.
100
117
  # Returns [op, left_str, right_str] or nil.
101
118
  def split_on_logical_op(source)
102
- depth = 0
103
119
  # Look for ' or ' first (lower precedence than 'and' in OCL?)
104
120
  # Actually OCL: 'or' < 'xor' < 'and' < 'implies'. We pick the
105
121
  # rightmost top-level operator (left-associative).
@@ -113,6 +129,45 @@ module Ea
113
129
  nil
114
130
  end
115
131
 
132
+ # Split on top-level comparison operator (>, <, >=, <=, =, ==, !=).
133
+ # Comparison has higher precedence than and/or, so we split here
134
+ # BEFORE calling split_on_logical_op.
135
+ def split_on_comparison_op(source)
136
+ %w[>= <= == != = > <].each do |op|
137
+ # Need to be careful with `>=` vs `>`: scan in order so
138
+ # multi-char operators are detected first.
139
+ idx = find_top_level_op(source, op)
140
+ next unless idx
141
+
142
+ return [op, source[0...idx], source[(idx + op.length)..]]
143
+ end
144
+ nil
145
+ end
146
+
147
+ def find_top_level_op(source, op) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
148
+ depth = 0
149
+ i = 0
150
+ while i < source.length
151
+ c = source[i]
152
+ depth += 1 if c == "("
153
+ depth -= 1 if c == ")"
154
+ if depth.zero? && source[i, op.length] == op
155
+ # Each `next` clause below MUST fall through to `i += 1`
156
+ # at the loop's tail — using `next` here would skip the
157
+ # increment and infinite-loop on the same matching char.
158
+ should_skip =
159
+ (op == ">" && source[i + 1, 1] == "=") || # >= matches as >
160
+ (op == "<" && source[i + 1, 1] == "=") || # <= matches as <
161
+ (op == "=" && source[i + 1, 1] == "=") || # == matches as =
162
+ (op == ">" && source[i - 1, 1] == "-") || # -> arrow
163
+ (op == "<" && source[i - 1, 1] == "-") # <- arrow
164
+ return i unless should_skip
165
+ end
166
+ i += 1
167
+ end
168
+ nil
169
+ end
170
+
116
171
  # Find substring at depth 0 (not inside parens).
117
172
  def find_top_level(source, substring)
118
173
  depth = 0
@@ -121,9 +176,8 @@ module Ea
121
176
  c = source[i]
122
177
  depth += 1 if c == "("
123
178
  depth -= 1 if c == ")"
124
- if depth.zero? && source[i, substring.length] == substring
125
- return i
126
- end
179
+ return i if depth.zero? && source[i, substring.length] == substring
180
+
127
181
  i += 1
128
182
  end
129
183
  nil
@@ -65,6 +65,51 @@ module Ea
65
65
  documents scripts
66
66
  ].freeze
67
67
 
68
+ # Maps EA SQL table names to collection accessor symbols.
69
+ # Centralised here (MECE) so validators and services don't
70
+ # each maintain their own case/when dispatch. Adding a new
71
+ # table = adding one hash entry.
72
+ TABLE_TO_COLLECTION = {
73
+ "t_package" => :packages,
74
+ "t_object" => :objects,
75
+ "t_attribute" => :attributes,
76
+ "t_operation" => :operations,
77
+ "t_operationparams" => :operation_params,
78
+ "t_connector" => :connectors,
79
+ "t_diagram" => :diagrams,
80
+ "t_diagramobjects" => :diagram_objects,
81
+ "t_diagramlinks" => :diagram_links,
82
+ "t_objectconstraint" => :object_constraints,
83
+ "t_objectproperties" => :object_properties,
84
+ "t_taggedvalue" => :tagged_values,
85
+ "t_attributetag" => :attribute_tags,
86
+ "t_xref" => :xrefs,
87
+ "t_stereotypes" => :stereotypes,
88
+ "t_datatypes" => :datatypes,
89
+ "t_constrainttypes" => :constraint_types,
90
+ "t_connectortypes" => :connector_types,
91
+ "t_diagramtypes" => :diagram_types,
92
+ "t_objecttypes" => :object_types,
93
+ "t_statustypes" => :status_types,
94
+ "t_complexitytypes" => :complexity_types,
95
+ "t_documents" => :documents,
96
+ "t_scripts" => :scripts,
97
+ }.freeze
98
+
99
+ # @param table_name [String] EA SQL table name (e.g., "t_object")
100
+ # @return [Array] the collection for that table, or [] if unknown
101
+ def collection_for_table(table_name)
102
+ name = TABLE_TO_COLLECTION[table_name]
103
+ return [] unless name
104
+
105
+ records = @collections[name]
106
+ return [] unless records
107
+
108
+ # t_object is wrapped in ObjectRepository — call .all to
109
+ # get the plain Array that validators expect.
110
+ records.is_a?(Repositories::ObjectRepository) ? records.all : records
111
+ end
112
+
68
113
  COLLECTION_ACCESSORS.each do |name|
69
114
  define_method(name) do
70
115
  @collections[name] || []
@@ -167,9 +212,19 @@ module Ea
167
212
  @diagram_links_by_id[id] || []
168
213
  end
169
214
 
170
- # Find an object by ID
215
+ # Find an object by ID. Uses the repository's primary-key index
216
+ # rather than a scan: the parentid walk in QeaToXmi's
217
+ # `cyclic_ancestry?` calls this once per ancestor hop per object.
218
+ #
219
+ # Hash lookup matches on eql?, so it would miss a Float id that a
220
+ # scan's == would have caught. No caller here can do that:
221
+ # t_object.Object_ID is declared Type::Integer and the model
222
+ # coerces on construction, and every caller passes either such an
223
+ # attribute or an explicit to_i. Deliberately left unguarded — a
224
+ # scan fallback on miss would put every absent-parent lookup in
225
+ # the ancestry walk back on a linear scan.
171
226
  def find_object(id)
172
- objects.find_by_key(:ea_object_id, id)
227
+ objects.find(id)
173
228
  end
174
229
 
175
230
  # Find object by ea_guid
@@ -91,7 +91,7 @@ module Ea
91
91
  def normalize_guid(guid)
92
92
  return guid if guid.nil?
93
93
 
94
- guid.to_s.gsub(/[{}]/, "").upcase
94
+ Ea::GuidFormat.strip_braces(guid).upcase
95
95
  end
96
96
  end
97
97
  end
@@ -81,23 +81,7 @@ module Ea
81
81
  const == 1
82
82
  end
83
83
 
84
- # Check if attribute is public
85
- # @return [Boolean]
86
- def public?
87
- scope&.downcase == "public"
88
- end
89
-
90
- # Check if attribute is private
91
- # @return [Boolean]
92
- def private?
93
- scope&.downcase == "private"
94
- end
95
-
96
- # Check if attribute is protected
97
- # @return [Boolean]
98
- def protected?
99
- scope&.downcase == "protected"
100
- end
84
+ include ScopePredicate
101
85
 
102
86
  # @return [Integer] pos for attribute ordering within parent
103
87
  def sort_position
@@ -119,10 +119,11 @@ module Ea
119
119
  connector_type == "Aggregation"
120
120
  end
121
121
 
122
- # Check if connector is a realization
122
+ # Check if connector is a realization. EA writes both the US
123
+ # and UK spellings depending on version/edition.
123
124
  # @return [Boolean]
124
125
  def realization?
125
- connector_type == "Realization"
126
+ %w[Realization Realisation].include?(connector_type)
126
127
  end
127
128
 
128
129
  # Check if source is aggregate
@@ -194,17 +194,22 @@ module Ea
194
194
  # Note — diagram note. Same situation as Text: rendering
195
195
  # hint, not a model element. Dropped.
196
196
  #
197
+ TRANSFORMER_TYPES = {
198
+ "Enumeration" => :enumeration,
199
+ "DataType" => :data_type,
200
+ "PrimitiveType" => :data_type,
201
+ "Class" => :class,
202
+ "Interface" => :class,
203
+ "Signal" => :signal,
204
+ "Object" => :instance,
205
+ "Association" => :association_element,
206
+ }.freeze
207
+
197
208
  # @return [Symbol, nil] Registry key or nil if not a UML model element
198
209
  def transformer_type
199
- if enumeration? || stereotype_is?("enumeration")
200
- :enumeration
201
- elsif data_type?
202
- :data_type
203
- elsif uml_class? || interface?
204
- :class
205
- elsif instance?
206
- :instance
207
- end
210
+ return :enumeration if stereotype_is?("enumeration")
211
+
212
+ TRANSFORMER_TYPES[object_type]
208
213
  end
209
214
 
210
215
  def stereotype_is?(expected)
@@ -106,23 +106,7 @@ module Ea
106
106
  styleex&.include?("Reception=1")
107
107
  end
108
108
 
109
- # Check if operation is public
110
- # @return [Boolean]
111
- def public?
112
- scope&.downcase == "public"
113
- end
114
-
115
- # Check if operation is private
116
- # @return [Boolean]
117
- def private?
118
- scope&.downcase == "private"
119
- end
120
-
121
- # Check if operation is protected
122
- # @return [Boolean]
123
- def protected?
124
- scope&.downcase == "protected"
125
- end
109
+ include ScopePredicate
126
110
 
127
111
  # @return [Integer] pos for operation ordering within parent
128
112
  def sort_position
@@ -10,6 +10,9 @@ module Ea
10
10
  attribute :xref_id, Lutaml::Model::Type::String
11
11
  attribute :name, Lutaml::Model::Type::String
12
12
  attribute :xref_type, Lutaml::Model::Type::String
13
+ attribute :visibility, Lutaml::Model::Type::String
14
+ attribute :behavior, Lutaml::Model::Type::String
15
+ attribute :partition, Lutaml::Model::Type::String
13
16
  attribute :client, Lutaml::Model::Type::String
14
17
  attribute :supplier, Lutaml::Model::Type::String
15
18
  attribute :description, Lutaml::Model::Type::String
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Models
6
+ # Mixin providing `public?`, `private?`, `protected?` scope
7
+ # predicates for any model that has a `scope` attribute
8
+ # (t_attribute.scope, t_operation.scope, t_object.scope).
9
+ #
10
+ # EA stores scope as a case-insensitive string ("Public",
11
+ # "Private", "Protected"). This module centralises the
12
+ # comparison so both EaAttribute and EaOperation share one
13
+ # implementation.
14
+ module ScopePredicate
15
+ def public?
16
+ scope&.downcase == "public"
17
+ end
18
+
19
+ def private?
20
+ scope&.downcase == "private"
21
+ end
22
+
23
+ def protected?
24
+ scope&.downcase == "protected"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
data/lib/ea/qea/models.rb CHANGED
@@ -6,6 +6,7 @@ module Ea
6
6
  module Qea
7
7
  module Models
8
8
  autoload :BaseModel, "ea/qea/models/base_model"
9
+ autoload :ScopePredicate, "ea/qea/models/scope_predicate"
9
10
  autoload :EaAttribute, "ea/qea/models/ea_attribute"
10
11
  autoload :EaAttributeTag, "ea/qea/models/ea_attribute_tag"
11
12
  autoload :EaComplexityType, "ea/qea/models/ea_complexity_type"
@@ -215,9 +215,12 @@ module Ea
215
215
 
216
216
  private
217
217
 
218
+ # First occurrence wins, matching what a scan would return —
219
+ # `find` and `find_by_key` must not disagree about which record
220
+ # answers for a duplicated key.
218
221
  def build_pk_index
219
222
  @pk_index = {}
220
- @records.each { |r| @pk_index[r.primary_key] = r }
223
+ @records.each { |r| @pk_index[r.primary_key] ||= r }
221
224
  end
222
225
  end
223
226
  end
@@ -128,54 +128,9 @@ module Ea
128
128
  # Maps table names to their corresponding collections in Database
129
129
  #
130
130
  # @param table [String] Table name (e.g., "t_package", "t_object")
131
- # @return [Array, nil] Collection array or nil if table not mapped
132
- def get_collection_for_table(table) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
133
- case table
134
- when "t_package"
135
- database.packages
136
- when "t_object"
137
- database.objects.all
138
- when "t_attribute"
139
- database.attributes
140
- when "t_operation"
141
- database.operations
142
- when "t_operationparams"
143
- database.operation_params
144
- when "t_connector"
145
- database.connectors
146
- when "t_diagram"
147
- database.diagrams
148
- when "t_diagramobjects"
149
- database.diagram_objects
150
- when "t_diagramlinks"
151
- database.diagram_links
152
- when "t_objectconstraint"
153
- database.object_constraints
154
- when "t_objectproperties"
155
- database.object_properties
156
- when "t_taggedvalue"
157
- database.tagged_values
158
- when "t_attributetag"
159
- database.attribute_tags
160
- when "t_xref"
161
- database.xrefs
162
- when "t_stereotypes"
163
- database.stereotypes
164
- when "t_datatypes"
165
- database.datatypes
166
- when "t_constrainttypes"
167
- database.constraint_types
168
- when "t_connectortypes"
169
- database.connector_types
170
- when "t_diagramtypes"
171
- database.diagram_types
172
- when "t_objecttypes"
173
- database.object_types
174
- when "t_statustypes"
175
- database.status_types
176
- when "t_complexitytypes"
177
- database.complexity_types
178
- end
131
+ # @return [Array] Collection array or [] if table not mapped
132
+ def get_collection_for_table(table)
133
+ database.collection_for_table(table)
179
134
  end
180
135
 
181
136
  # Adds an error to the validation result
@@ -24,18 +24,30 @@ module Ea
24
24
  return unless ast
25
25
 
26
26
  if evaluate(ast, owner)
27
- result.add(:info, "OCL constraint '#{constraint.constraint_type}' " \
28
- "passed for #{owner.name}",
29
- entity_name: owner.name)
27
+ result.add_info(
28
+ category: :ocl_constraint,
29
+ entity_type: :class,
30
+ entity_id: owner.ea_object_id.to_s,
31
+ entity_name: owner.name,
32
+ message: "OCL constraint '#{constraint.constraint_type}' passed",
33
+ )
30
34
  else
31
- result.add(:warning, "OCL constraint '#{constraint.constraint_type}' " \
32
- "failed for #{owner.name}: #{constraint.constraint}",
33
- entity_name: owner.name)
35
+ result.add_warning(
36
+ category: :ocl_constraint,
37
+ entity_type: :class,
38
+ entity_id: owner.ea_object_id.to_s,
39
+ entity_name: owner.name,
40
+ message: "OCL constraint '#{constraint.constraint_type}' failed: #{constraint.constraint}",
41
+ )
34
42
  end
35
43
  rescue Ea::Ocl::UnsupportedError => e
36
- result.add(:info, "Skipping OCL constraint on #{owner&.name} " \
37
- "(unsupported syntax): #{e.message}",
38
- entity_name: owner&.name)
44
+ result.add_info(
45
+ category: :ocl_constraint,
46
+ entity_type: :class,
47
+ entity_id: owner&.ea_object_id.to_s,
48
+ entity_name: owner&.name,
49
+ message: "Skipping OCL constraint (unsupported syntax): #{e.message}",
50
+ )
39
51
  end
40
52
 
41
53
  # Parse the OCL body. Returns nil for empty / non-invariant
@@ -51,13 +63,11 @@ module Ea
51
63
  end
52
64
 
53
65
  def owner_for(constraint)
54
- object_id = constraint.respond_to?(:ea_object_id) ?
55
- constraint.ea_object_id :
56
- constraint.object_id
66
+ object_id = constraint.ea_object_id
57
67
  return nil unless object_id
58
68
 
59
69
  (database.collections[:objects] || [])
60
- .find { |o| o.object_id == object_id }
70
+ .find { |o| o.ea_object_id == object_id }
61
71
  end
62
72
  end
63
73
  end
@@ -36,9 +36,7 @@ module Ea
36
36
  end
37
37
 
38
38
  def escape(text)
39
- text.to_s.gsub("&", "&amp;")
40
- .gsub("<", "&lt;")
41
- .gsub(">", "&gt;")
39
+ XmlEscape.call(text)
42
40
  end
43
41
 
44
42
  def render_rect(p, fill, stroke)
@@ -425,6 +425,7 @@ module Ea
425
425
  end
426
426
  boxes
427
427
  end
428
+ public :parse_label_boxes
428
429
 
429
430
  # Parse one label box's colon-separated key=value body into
430
431
  # a Hash with string keys. Returns nil if OX/OY are absent.
@@ -17,7 +17,7 @@ module Ea
17
17
  def from_guid(ea_guid)
18
18
  return nil if ea_guid.nil? || ea_guid.empty?
19
19
 
20
- ea_guid.to_s.gsub(/[{}]/, "")
20
+ Ea::GuidFormat.strip_braces(ea_guid)
21
21
  end
22
22
 
23
23
  # Convert a GUID to the EAID_ filename format used by EA's
@@ -26,7 +26,7 @@ module Ea
26
26
  def to_eaid(ea_guid)
27
27
  return nil if ea_guid.nil? || ea_guid.empty?
28
28
 
29
- "EAID_" + ea_guid.to_s.gsub(/[{}]/, "").tr("-", "_")
29
+ Ea::GuidFormat.ea_guid_to_xmi_id(ea_guid, prefix: "EAID")
30
30
  end
31
31
 
32
32
  def synthetic(prefix, *parts)
@@ -1,4 +1,5 @@
1
- # frozen_string_string: true
1
+ # frozen_string_literal: true
2
+
2
3
 
3
4
  module Ea
4
5
  module Svg
@@ -55,12 +55,7 @@ module Ea
55
55
  private
56
56
 
57
57
  def visibility_marker
58
- case property.visibility
59
- when "private" then "-"
60
- when "protected" then "#"
61
- when "package" then "~"
62
- else "+"
63
- end
58
+ VisibilitySymbol.for(property.visibility)
64
59
  end
65
60
 
66
61
  # Property name, prefixed with the declaring classifier's
@@ -55,12 +55,7 @@ module Ea
55
55
  end
56
56
 
57
57
  def self.visibility_prefix(operation)
58
- case operation.visibility
59
- when "private" then "- "
60
- when "protected" then "# "
61
- when "package" then "~ "
62
- else "+ "
63
- end
58
+ VisibilitySymbol.for(operation.visibility, with_space: true)
64
59
  end
65
60
  private_class_method :visibility_prefix
66
61
 
@@ -71,7 +66,7 @@ module Ea
71
66
 
72
67
  "#{text}: #{operation.return_type_name}"
73
68
  end
74
- private_class_method :operation_text
69
+ public_class_method :operation_text
75
70
 
76
71
  def self.namespace_double_colon(type_name)
77
72
  return "" if type_name.nil? || type_name.empty?
@@ -185,9 +185,6 @@ module Ea
185
185
  # alphabetical "+ Name" rows in the body compartment, but
186
186
  # only when the diagram's t_diagram.ShowPackageContents flag
187
187
  # is non-zero. Returns [] when the diagram suppresses
188
- # package contents or the model element isn't a Package.
189
- # Each entry is a Struct(name:, kind:) so the renderer can
190
- # pick the correct per-type icon (Enumeration vs default).
191
188
  def package_content_lines_for(model_element)
192
189
  return [] unless model_element.is_a?(Ea::Model::Package)
193
190
  return [] unless diagram.show_package_contents
@@ -199,6 +196,7 @@ module Ea
199
196
  PackageContentRow.new(name: c.name.to_s, kind: row_icon_kind(c))
200
197
  end
201
198
  end
199
+ public :package_content_lines_for
202
200
 
203
201
  def package_children(package)
204
202
  return [] unless model_index
@@ -1,4 +1,5 @@
1
- # frozen_string: true
1
+ # frozen_string_literal: true
2
+
2
3
 
3
4
  module Ea
4
5
  module Svg
@@ -236,12 +236,7 @@ module Ea
236
236
  end
237
237
 
238
238
  def visibility_prefix(property)
239
- case property.visibility
240
- when "private" then "-"
241
- when "protected" then "#"
242
- when "package" then "~"
243
- else "+"
244
- end
239
+ VisibilitySymbol.for(property.visibility)
245
240
  end
246
241
 
247
242
  def text_at(x, y, content)
@@ -1,4 +1,4 @@
1
- # frozen_string: true
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Ea
4
4
  module Svg