ea 0.5.2 → 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 (142) 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 +9 -24
  44. data/TODO.complete/65-qeatoxmi-diagrams.md +9 -23
  45. data/TODO.complete/66-qeatoxmi-style-tags-docs.md +25 -18
  46. data/TODO.complete/67-ocl-collection-ops.md +12 -12
  47. data/TODO.complete/68-ocl-comparison-ops.md +16 -11
  48. data/TODO.complete/69-spec-new-models.md +13 -18
  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/export.rb +10 -1
  69. data/lib/ea/cli/command/mdg.rb +1 -10
  70. data/lib/ea/cli/command/render.rb +2 -1
  71. data/lib/ea/diff/html_reporter.rb +1 -5
  72. data/lib/ea/export/json/generator.rb +5 -6
  73. data/lib/ea/export/plantuml/generator.rb +5 -3
  74. data/lib/ea/guid_format.rb +91 -0
  75. data/lib/ea/mdg/document.rb +2 -1
  76. data/lib/ea/mdg/loader.rb +2 -1
  77. data/lib/ea/mdg/registry.rb +57 -2
  78. data/lib/ea/mdg/xml/applies_to.rb +1 -1
  79. data/lib/ea/mdg/xml/apply.rb +1 -1
  80. data/lib/ea/mdg/xml/content.rb +1 -1
  81. data/lib/ea/mdg/xml/documentation.rb +1 -1
  82. data/lib/ea/mdg/xml/stereotype.rb +1 -1
  83. data/lib/ea/mdg/xml/stereotypes.rb +1 -1
  84. data/lib/ea/mdg/xml/tag.rb +1 -1
  85. data/lib/ea/mdg/xml/tagged_values.rb +1 -1
  86. data/lib/ea/mdg/xml/technology.rb +1 -1
  87. data/lib/ea/mdg/xml/uml_profile.rb +1 -1
  88. data/lib/ea/mdg/xml/uml_profiles.rb +1 -1
  89. data/lib/ea/mdg/xml.rb +2 -1
  90. data/lib/ea/mdg.rb +2 -1
  91. data/lib/ea/model/ghost_label.rb +2 -1
  92. data/lib/ea/ocl/evaluator.rb +4 -2
  93. data/lib/ea/qea/database.rb +57 -2
  94. data/lib/ea/qea/factory/reference_resolver.rb +1 -1
  95. data/lib/ea/qea/models/ea_attribute.rb +1 -17
  96. data/lib/ea/qea/models/ea_connector.rb +3 -2
  97. data/lib/ea/qea/models/ea_object.rb +14 -9
  98. data/lib/ea/qea/models/ea_operation.rb +1 -17
  99. data/lib/ea/qea/models/ea_xref.rb +3 -0
  100. data/lib/ea/qea/models/scope_predicate.rb +29 -0
  101. data/lib/ea/qea/models.rb +1 -0
  102. data/lib/ea/qea/repositories/base_repository.rb +4 -1
  103. data/lib/ea/qea/validation/base_validator.rb +3 -48
  104. data/lib/ea/qea/validation/database/ocl_constraint_validator.rb +23 -13
  105. data/lib/ea/shapescript/renderer.rb +1 -3
  106. data/lib/ea/sources/qea/diagram_builder.rb +1 -0
  107. data/lib/ea/sources/qea/id_normalizer.rb +2 -2
  108. data/lib/ea/svg/ea_emitter/background.rb +2 -1
  109. data/lib/ea/svg/ea_emitter/element/attribute_line_builder.rb +1 -6
  110. data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +2 -7
  111. data/lib/ea/svg/ea_emitter/elements.rb +1 -3
  112. data/lib/ea/svg/ea_emitter/ghost_labels.rb +2 -1
  113. data/lib/ea/svg/ea_emitter/label/end_label.rb +1 -6
  114. data/lib/ea/svg/ea_emitter/marker/package_import.rb +1 -1
  115. data/lib/ea/svg/ea_emitter/markers.rb +52 -23
  116. data/lib/ea/svg/ea_emitter/text_renderer.rb +1 -4
  117. data/lib/ea/svg/ea_emitter/visibility_symbol.rb +37 -0
  118. data/lib/ea/svg/ea_emitter.rb +1 -0
  119. data/lib/ea/svg/parity/source.rb +0 -2
  120. data/lib/ea/svg/parity/suite.rb +0 -1
  121. data/lib/ea/transformers/qea_to_xmi/cardinality.rb +40 -28
  122. data/lib/ea/transformers/qea_to_xmi/context.rb +35 -1
  123. data/lib/ea/transformers/qea_to_xmi/extension_serializer.rb +686 -0
  124. data/lib/ea/transformers/qea_to_xmi/guid_format.rb +5 -48
  125. data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +55 -54
  126. data/lib/ea/transformers/qea_to_xmi/primitive_types.rb +105 -0
  127. data/lib/ea/transformers/qea_to_xmi/profile_serializer.rb +163 -0
  128. data/lib/ea/transformers/qea_to_xmi/run_state.rb +7 -6
  129. data/lib/ea/transformers/qea_to_xmi/transformer.rb +396 -185
  130. data/lib/ea/transformers/qea_to_xmi/visibility.rb +30 -19
  131. data/lib/ea/transformers/qea_to_xmi.rb +5 -2
  132. data/lib/ea/transformers.rb +7 -2
  133. data/lib/ea/validation.rb +1 -1
  134. data/lib/ea/version.rb +1 -1
  135. data/lib/ea/xmi/liquid_drops/attribute_drop.rb +12 -4
  136. data/lib/ea/xmi/liquid_drops/klass_drop.rb +1 -2
  137. data/lib/ea/xmi/lookup_service.rb +4 -0
  138. data/lib/ea/xmi/parser.rb +30 -1
  139. data/lib/ea/xml_escape.rb +21 -0
  140. data/lib/ea.rb +2 -0
  141. metadata +28 -11
  142. data/lutaml-uml-0.5.1.gem +0 -0
@@ -1,4 +1,5 @@
1
- # frozen_string: true
1
+ # frozen_string_literal: true
2
+
2
3
 
3
4
  module Ea
4
5
  module Model
@@ -32,6 +32,8 @@ module Ea
32
32
  return nil unless context
33
33
 
34
34
  context.public_send(name)
35
+ rescue NoMethodError
36
+ nil
35
37
  end
36
38
 
37
39
  def eval_string_matches(ast, context)
@@ -63,14 +65,14 @@ module Ea
63
65
 
64
66
  def eval_size(ast, context)
65
67
  collection = evaluate(ast.collection, context)
66
- return 0 unless collection.respond_to?(:size)
68
+ return 0 unless collection.is_a?(Array)
67
69
 
68
70
  collection.size
69
71
  end
70
72
 
71
73
  def eval_is_empty(ast, context)
72
74
  collection = evaluate(ast.collection, context)
73
- return true unless collection.respond_to?(:empty?)
75
+ return true unless collection.is_a?(Array)
74
76
 
75
77
  collection.empty?
76
78
  end
@@ -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("&", "&")
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
@@ -62,7 +62,7 @@ module Ea
62
62
  # Backwards-compatible API: count of marker bodies emitted.
63
63
  def count_for(connector)
64
64
  return 0 if connector.hidden
65
- return 0 unless connector.waypoints&.any? { |w| w.position }
65
+ return 0 unless connector.waypoints&.any?(&:position)
66
66
 
67
67
  entries_for(connector).size
68
68
  end
@@ -85,15 +85,51 @@ module Ea
85
85
  end
86
86
  end
87
87
 
88
+ # OCP registries for marker shapes. Adding a new marker shape
89
+ # = adding one entry to SHAPE_REGISTRY, not editing 3 case/when
90
+ # branches. Each entry carries the style_key and a render
91
+ # lambda that knows how to draw the shape.
92
+ #
93
+ # Style keys (:diamond_filled, :triangle_open, :connector_line)
94
+ # map to CSS strings via STYLE_MAP below.
95
+ SHAPE_REGISTRY = {
96
+ diamond: {
97
+ style_key: :diamond_filled,
98
+ render: ->(m, anchor, base) { m.diamond_polygon(anchor, base) }
99
+ },
100
+ triangle: {
101
+ style_key: :triangle_open,
102
+ render: ->(m, anchor, base) { m.triangle_polygon(anchor, base) }
103
+ },
104
+ plus: {
105
+ style_key: :connector_line,
106
+ render: ->(m, anchor, _base) { m.plus_path(anchor) }
107
+ },
108
+ arrow: {
109
+ style_key: :connector_line,
110
+ render: ->(m, anchor, base) { m.arrow_path(anchor, base) }
111
+ },
112
+ package_anchor: {
113
+ style_key: :connector_line,
114
+ render: ->(m, anchor, base) { m.package_anchor_path(anchor, base) }
115
+ }
116
+ }.freeze
117
+
118
+ # Style key → (fill, opacity) pair. The common stroke prefix
119
+ # is shared, so only the fill and opacity vary.
120
+ STYLE_MAP = {
121
+ diamond_filled: { fill: "#000000", opacity: "1.00" },
122
+ triangle_open: { fill: "#FFFFFF", opacity: "1.00" },
123
+ connector_line: { fill: "#000000", opacity: "0.00" }
124
+ }.freeze
125
+
88
126
  def style_for(key)
89
- case key
90
- when :diamond_filled
91
- "stroke-width:#{stroke_width};stroke-linecap:round;stroke-linejoin:bevel; fill:#000000;fill-opacity:1.00; stroke:#000000; stroke-opacity:1.00"
92
- when :triangle_open
93
- "stroke-width:#{stroke_width};stroke-linecap:round;stroke-linejoin:bevel; fill:#FFFFFF;fill-opacity:1.00; stroke:#000000; stroke-opacity:1.00"
94
- when :connector_line
95
- "stroke-width:#{stroke_width};stroke-linecap:round;stroke-linejoin:bevel; fill:#000000;fill-opacity:0.00; stroke:#000000; stroke-opacity:1.00"
96
- end
127
+ spec = STYLE_MAP[key]
128
+ return nil unless spec
129
+
130
+ "stroke-width:#{stroke_width};stroke-linecap:round;stroke-linejoin:bevel; " \
131
+ "fill:#{spec[:fill]};fill-opacity:#{spec[:opacity]}; " \
132
+ "stroke:#000000; stroke-opacity:1.00"
97
133
  end
98
134
 
99
135
  def visible_connectors
@@ -210,13 +246,7 @@ module Ea
210
246
  end
211
247
 
212
248
  def style_key_for(spec)
213
- case spec.shape
214
- when :diamond then :diamond_filled
215
- when :triangle then :triangle_open
216
- when :plus then :connector_line # shares style with line <g>
217
- when :arrow then :connector_line # share with line <g>
218
- when :package_anchor then :connector_line
219
- end
249
+ SHAPE_REGISTRY.dig(spec.shape, :style_key)
220
250
  end
221
251
 
222
252
  # Rounded [x, y] of the marker's tip (anchor) in canvas
@@ -227,13 +257,10 @@ module Ea
227
257
  end
228
258
 
229
259
  def render_shape(spec)
230
- case spec.shape
231
- when :diamond then diamond_polygon(spec.anchor, spec.base)
232
- when :triangle then triangle_polygon(spec.anchor, spec.base)
233
- when :plus then plus_path(spec.anchor)
234
- when :arrow then arrow_path(spec.anchor, spec.base)
235
- when :package_anchor then package_anchor_path(spec.anchor, spec.base)
236
- end
260
+ entry = SHAPE_REGISTRY[spec.shape]
261
+ return nil unless entry
262
+
263
+ entry[:render].call(self, spec.anchor, spec.base)
237
264
  end
238
265
 
239
266
  # Plus marker: two crossed line segments at the anchor.
@@ -384,6 +411,8 @@ module Ea
384
411
 
385
412
  [dx / len, dy / len]
386
413
  end
414
+ public :diamond_polygon, :triangle_polygon, :plus_path,
415
+ :arrow_path, :package_anchor_path
387
416
 
388
417
  def translate_point(p)
389
418
  return p unless @canvas
@@ -81,10 +81,7 @@ module Ea
81
81
  end
82
82
 
83
83
  def escaped_content
84
- content.gsub("&", "&amp;")
85
- .gsub("<", "&lt;")
86
- .gsub(">", "&gt;")
87
- .gsub("\"", "&quot;")
84
+ XmlEscape.call(content)
88
85
  end
89
86
  end
90
87
  end