lutaml 0.10.0 → 0.10.1

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +73 -31
  3. data/Gemfile +2 -5
  4. data/TODO.bugfix.md +24 -0
  5. data/lib/lutaml/cli/enhanced_formatter.rb +4 -2
  6. data/lib/lutaml/cli/tree_view_formatter.rb +1 -1
  7. data/lib/lutaml/cli/uml/diagram_command.rb +2 -6
  8. data/lib/lutaml/converter/xmi_to_uml.rb +11 -7
  9. data/lib/lutaml/ea/diagram/extractor.rb +8 -3
  10. data/lib/lutaml/model_transformations/parsers/base_parser.rb +90 -13
  11. data/lib/lutaml/qea/factory/class_transformer.rb +8 -19
  12. data/lib/lutaml/qea/factory/data_type_transformer.rb +2 -9
  13. data/lib/lutaml/qea/factory/diagram_transformer.rb +0 -3
  14. data/lib/lutaml/qea/factory/enum_transformer.rb +3 -5
  15. data/lib/lutaml/qea/factory/generalization_transformer.rb +1 -1
  16. data/lib/lutaml/qea/factory/package_transformer.rb +45 -19
  17. data/lib/lutaml/qea/validation/base_validator.rb +5 -0
  18. data/lib/lutaml/qea/verification/document_normalizer.rb +4 -12
  19. data/lib/lutaml/qea/verification/document_verifier.rb +25 -15
  20. data/lib/lutaml/uml_repository/index_builder.rb +27 -9
  21. data/lib/lutaml/uml_repository/presenters/diagram_presenter.rb +2 -7
  22. data/lib/lutaml/uml_repository/queries/inheritance_query.rb +66 -0
  23. data/lib/lutaml/uml_repository/queries/search_query.rb +1 -1
  24. data/lib/lutaml/uml_repository/repository.rb +34 -0
  25. data/lib/lutaml/uml_repository/validators/repository_validator.rb +1 -1
  26. data/lib/lutaml/uml_repository/web_ui/app.rb +25 -7
  27. data/lib/lutaml/version.rb +1 -1
  28. data/lib/lutaml/xmi/parsers/xmi_base.rb +67 -150
  29. data/lutaml.gemspec +3 -0
  30. metadata +45 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e09b3be9ab70b7039e7144deb3e582e09fc68ff93d0b5798dded8a60b01b584
4
- data.tar.gz: 871aa8320e9de1d51ca65230ec22c1f3908e67e72014b6ea66287a266a01bf48
3
+ metadata.gz: 380893deae7b963ad1177e3cec77bcb1752dfb15efd8560a952535e58bdec608
4
+ data.tar.gz: cab5e523724906b6a9ff202a335195c73b7762521acc8550705b1fae66784edf
5
5
  SHA512:
6
- metadata.gz: d4e542c816f86d346ff0c6010574746f3ff865eee49281036e2f15500d6b884d61e53de5473438b687f0135040a48bb976f535fc1178d5bf621fc48623504afb
7
- data.tar.gz: 3bcefa9715e153847054b601510f7311249a713c849891aa81b7f077a6145ebb0a494fc8e4136a99f84fb2f70c59f267d5ba9e027a8f148b1369ca0a80be651f
6
+ metadata.gz: 7332ae8e47d87b493f825a017aef535f9002997dd729b8b7f858e7361225aab4e20428bb64e208ff4b46d4335e166566c4277c68b9a04e0874197aeb42acd475
7
+ data.tar.gz: 864cbb3fc456851627645c86083c08e52599c65dd2867f4014f956b114708fe952ccb97c00a526f2d41b2d9c7d725f8b990081b1a30ddaae899fd89c9dc65e01
data/.rubocop_todo.yml CHANGED
@@ -1,18 +1,47 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-04-15 12:28:57 UTC using RuboCop version 1.86.1.
3
+ # on 2026-04-20 02:27:04 UTC using RuboCop version 1.86.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 55
9
+ # Offense count: 11
10
+ # This cop supports safe autocorrection (--autocorrect).
11
+ # Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation.
12
+ Bundler/OrderedGems:
13
+ Exclude:
14
+ - 'Gemfile'
15
+
16
+ # Offense count: 2
17
+ # This cop supports safe autocorrection (--autocorrect).
18
+ Layout/ClosingParenthesisIndentation:
19
+ Exclude:
20
+ - 'lib/lutaml/qea/verification/document_verifier.rb'
21
+
22
+ # Offense count: 2
23
+ # This cop supports safe autocorrection (--autocorrect).
24
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
25
+ # SupportedStyles: consistent, consistent_relative_to_receiver, special_for_inner_method_call, special_for_inner_method_call_in_parentheses
26
+ Layout/FirstArgumentIndentation:
27
+ Exclude:
28
+ - 'lib/lutaml/qea/verification/document_verifier.rb'
29
+
30
+ # Offense count: 63
10
31
  # This cop supports safe autocorrection (--autocorrect).
11
32
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
12
33
  # URISchemes: http, https
13
34
  Layout/LineLength:
14
35
  Enabled: false
15
36
 
37
+ # Offense count: 2
38
+ # This cop supports safe autocorrection (--autocorrect).
39
+ # Configuration parameters: EnforcedStyle.
40
+ # SupportedStyles: symmetrical, new_line, same_line
41
+ Layout/MultilineMethodCallBraceLayout:
42
+ Exclude:
43
+ - 'lib/lutaml/qea/verification/document_verifier.rb'
44
+
16
45
  # Offense count: 1
17
46
  Lint/BinaryOperatorWithIdenticalOperands:
18
47
  Exclude:
@@ -29,37 +58,34 @@ Lint/ConstantDefinitionInBlock:
29
58
  - 'spec/lutaml/model_transformations_spec.rb'
30
59
  - 'spec/lutaml/uml_repository/presenters/presenter_factory_spec.rb'
31
60
 
32
- # Offense count: 9
61
+ # Offense count: 8
33
62
  # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
34
63
  Lint/DuplicateBranch:
35
64
  Exclude:
36
65
  - 'lib/lutaml/cli/element_identifier.rb'
37
66
  - 'lib/lutaml/cli/uml/verify_command.rb'
38
67
  - 'lib/lutaml/qea/factory/base_transformer.rb'
39
- - 'lib/lutaml/qea/factory/class_transformer.rb'
40
68
  - 'lib/lutaml/qea/models/ea_datatype.rb'
41
69
  - 'lib/lutaml/qea/validation/validation_engine.rb'
42
70
  - 'lib/lutaml/uml_repository/queries/package_query.rb'
43
71
  - 'lib/lutaml/uml_repository/static_site/configuration.rb'
44
72
  - 'lib/lutaml/uml_repository/static_site/search_index_builder.rb'
45
73
 
46
- # Offense count: 6
74
+ # Offense count: 4
47
75
  # Configuration parameters: AllowComments, AllowEmptyLambdas.
48
76
  Lint/EmptyBlock:
49
77
  Exclude:
50
- - 'spec/lutaml/ea/diagram/svg_accuracy_spec.rb'
51
78
  - 'spec/lutaml/qea/integration/tagged_values_integration_spec.rb'
52
79
  - 'spec/lutaml/qea/services/database_loader_spec.rb'
53
80
  - 'spec/lutaml/uml_repository/package_exporter_spec.rb'
54
81
  - 'spec/lutaml/uml_repository/repository_spec.rb'
55
82
 
56
- # Offense count: 5
83
+ # Offense count: 3
57
84
  # This cop supports safe autocorrection (--autocorrect).
58
85
  # Configuration parameters: AllowComments.
59
86
  Lint/EmptyConditionalBody:
60
87
  Exclude:
61
88
  - 'spec/integration/qea_xmi_equivalency_spec.rb'
62
- - 'spec/lutaml/ea/diagram/svg_accuracy_spec.rb'
63
89
  - 'spec/lutaml/qea/verification/equivalence_integration_spec.rb'
64
90
 
65
91
  # Offense count: 4
@@ -81,6 +107,41 @@ Lint/UnusedMethodArgument:
81
107
  Exclude:
82
108
  - 'lib/lutaml/cli/uml/export_command.rb'
83
109
 
110
+ # Offense count: 3
111
+ # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
112
+ Metrics/AbcSize:
113
+ Exclude:
114
+ - 'lib/lutaml/model_transformations/parsers/base_parser.rb'
115
+ - 'lib/lutaml/uml_repository/index_builder.rb'
116
+ - 'lib/lutaml/uml_repository/queries/inheritance_query.rb'
117
+
118
+ # Offense count: 1
119
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
120
+ # AllowedMethods: refine
121
+ Metrics/BlockLength:
122
+ Max: 28
123
+
124
+ # Offense count: 4
125
+ # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
126
+ Metrics/CyclomaticComplexity:
127
+ Exclude:
128
+ - 'lib/lutaml/cli/tree_view_formatter.rb'
129
+ - 'lib/lutaml/converter/xmi_to_uml.rb'
130
+ - 'lib/lutaml/uml_repository/queries/inheritance_query.rb'
131
+ - 'lib/lutaml/uml_repository/queries/search_query.rb'
132
+
133
+ # Offense count: 5
134
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
135
+ Metrics/MethodLength:
136
+ Max: 14
137
+
138
+ # Offense count: 2
139
+ # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
140
+ Metrics/PerceivedComplexity:
141
+ Exclude:
142
+ - 'lib/lutaml/qea/factory/enum_transformer.rb'
143
+ - 'lib/lutaml/uml_repository/queries/inheritance_query.rb'
144
+
84
145
  # Offense count: 2
85
146
  # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
86
147
  # AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to
@@ -145,7 +206,7 @@ RSpec/ContextWording:
145
206
  RSpec/DescribeClass:
146
207
  Enabled: false
147
208
 
148
- # Offense count: 650
209
+ # Offense count: 651
149
210
  # Configuration parameters: CountAsOne.
150
211
  RSpec/ExampleLength:
151
212
  Max: 108
@@ -213,11 +274,11 @@ RSpec/MessageSpies:
213
274
  - 'spec/lutaml/uml_repository/presenters/diagram_presenter_spec.rb'
214
275
  - 'spec/lutaml/uml_repository/repository_spec.rb'
215
276
 
216
- # Offense count: 940
277
+ # Offense count: 943
217
278
  RSpec/MultipleExpectations:
218
279
  Max: 26
219
280
 
220
- # Offense count: 116
281
+ # Offense count: 118
221
282
  # Configuration parameters: AllowSubject.
222
283
  RSpec/MultipleMemoizedHelpers:
223
284
  Max: 11
@@ -235,20 +296,7 @@ RSpec/NoExpectationExample:
235
296
  - 'spec/lutaml/qea/verification/comprehensive_equivalence_spec.rb'
236
297
  - 'spec/lutaml/uml_repository/static_site/generator_spec.rb'
237
298
 
238
- # Offense count: 19
239
- RSpec/PendingWithoutReason:
240
- Exclude:
241
- - 'spec/lutaml/model_transformations/parsers/base_parser_spec.rb'
242
- - 'spec/lutaml/model_transformations/parsers/xmi_parser_spec.rb'
243
- - 'spec/lutaml/qea/integration/full_parsing_spec.rb'
244
- - 'spec/lutaml/qea/validation/package_validator_spec.rb'
245
- - 'spec/lutaml/uml_repository/package_exporter_spec.rb'
246
- - 'spec/lutaml/uml_repository/package_loader_spec.rb'
247
- - 'spec/lutaml/uml_repository/queries/inheritance_query_spec.rb'
248
- - 'spec/lutaml/uml_repository/validators/repository_validator_spec.rb'
249
- - 'spec/lutaml/uml_repository/web_ui/app_spec.rb'
250
-
251
- # Offense count: 14
299
+ # Offense count: 16
252
300
  RSpec/RepeatedExample:
253
301
  Exclude:
254
302
  - 'spec/lutaml/cli/package_commands_spec.rb'
@@ -316,12 +364,6 @@ Style/EvalWithLocation:
316
364
  Exclude:
317
365
  - 'spec/lutaml/cli/uml/search_command_spec.rb'
318
366
 
319
- # Offense count: 2
320
- # This cop supports unsafe autocorrection (--autocorrect-all).
321
- Style/IdenticalConditionalBranches:
322
- Exclude:
323
- - 'lib/lutaml/qea/factory/class_transformer.rb'
324
-
325
367
  # Offense count: 1
326
368
  # This cop supports unsafe autocorrection (--autocorrect-all).
327
369
  Style/PartitionInsteadOfDoubleSelect:
data/Gemfile CHANGED
@@ -8,13 +8,10 @@ gemspec
8
8
  gem "parsanol", "1.3.9"
9
9
 
10
10
  gem "canon"
11
- gem "listen"
12
- gem "lutaml-model", github: "lutaml/lutaml-model", ref: "1dbe006"
11
+ gem "lutaml-model", github: "lutaml/lutaml-model", branch: "main"
12
+ gem "moxml", github: "lutaml/moxml", branch: "main"
13
13
  gem "openssl", "~> 3.0"
14
- gem "pry"
15
- gem "puma"
16
14
  gem "rack-test"
17
- gem "rackup"
18
15
  gem "rake"
19
16
  gem "rspec"
20
17
  gem "rubocop"
data/TODO.bugfix.md ADDED
@@ -0,0 +1,24 @@
1
+ # Bug Fixes (All Resolved)
2
+
3
+ ## 1. DiagramTransformer overwrites resolved package_id with raw numeric ID [FIXED]
4
+
5
+ **File:** `lib/lutaml/qea/factory/diagram_transformer.rb`
6
+
7
+ Lines 34-39 correctly resolve the numeric `package_id` to a GUID (`"EAPK_..."`), but line 66
8
+ overwrote it with `ea_diagram.package_id` (a raw integer). Removed the overwrite.
9
+
10
+ ## 2. Stereotype assigned as string instead of array in multiple transformers [FIXED]
11
+
12
+ `TopElement#stereotype` is declared `collection: true` (should be an array), but multiple
13
+ transformers assigned a bare string. lutaml-model doesn't auto-wrap. Downstream code calling
14
+ `.first` or `.include?` got string behavior (first character, substring match) instead of array.
15
+
16
+ Fixed in:
17
+ - QEA factory: class_transformer, data_type_transformer, enum_transformer, package_transformer, generalization_transformer
18
+ - XMI converter: xmi_to_uml.rb (all 5 assignments)
19
+ - Consumers: enhanced_formatter, tree_view_formatter, diagram_command, diagram_presenter, search_query
20
+
21
+ ## 3. Dead ternary in ClassTransformer [FIXED]
22
+
23
+ `klass.type = is_text_class ? "Class" : "Class"` — both branches were identical.
24
+ Replaced with `klass.type = "Class"`.
@@ -140,8 +140,10 @@ module Lutaml
140
140
  if klass.respond_to?(:xmi_id)
141
141
  lines << " XMI ID: #{klass.xmi_id}"
142
142
  end
143
- if klass.respond_to?(:stereotype) && klass.stereotype
144
- lines << " Stereotype: #{klass.stereotype}"
143
+ if klass.respond_to?(:stereotype) && klass.stereotype && !klass.stereotype.empty?
144
+ st = klass.stereotype
145
+ st_str = st.is_a?(Array) ? st.join(", ") : st
146
+ lines << " Stereotype: #{st_str}"
145
147
  end
146
148
  if klass.respond_to?(:is_abstract)
147
149
  lines << " Abstract: #{klass.is_abstract ? 'Yes' : 'No'}"
@@ -281,7 +281,7 @@ module Lutaml
281
281
  def determine_class_type(klass)
282
282
  return :enumeration if klass.class.name&.include?("Enum")
283
283
  return :interface if klass.respond_to?(:stereotype) &&
284
- klass.stereotype&.downcase == "interface"
284
+ Array(klass.stereotype).any? { |s| s&.downcase == "interface" }
285
285
 
286
286
  :class
287
287
  end
@@ -83,12 +83,8 @@ module Lutaml
83
83
  }
84
84
 
85
85
  # Add stereotype if available
86
- if uml_element.respond_to?(:stereotype) && uml_element.stereotype
87
- element_data[:stereotype] = if uml_element.stereotype.is_a?(Array)
88
- uml_element.stereotype.first
89
- else
90
- uml_element.stereotype
91
- end
86
+ if uml_element.respond_to?(:stereotype) && uml_element.stereotype && !uml_element.stereotype.empty?
87
+ element_data[:stereotype] = uml_element.stereotype.first
92
88
  end
93
89
 
94
90
  # Add attributes and operations for classes
@@ -27,7 +27,8 @@ module Lutaml
27
27
  pkg.xmi_id = package.id
28
28
  pkg.name = get_package_name(package)
29
29
  pkg.definition = doc_node_attribute_value(package.id, "documentation")
30
- pkg.stereotype = doc_node_attribute_value(package.id, "stereotype")
30
+ st = doc_node_attribute_value(package.id, "stereotype")
31
+ pkg.stereotype = [st] if st
31
32
 
32
33
  pkg.packages = create_uml_packages(package)
33
34
  pkg.classes = create_uml_classes(package)
@@ -58,7 +59,8 @@ module Lutaml
58
59
  k.type = klass.type.split(":").last
59
60
  k.is_abstract = doc_node_attribute_value(klass.id, "isAbstract")
60
61
  k.definition = doc_node_attribute_value(klass.id, "documentation")
61
- k.stereotype = doc_node_attribute_value(klass.id, "stereotype")
62
+ k_st = doc_node_attribute_value(klass.id, "stereotype")
63
+ k.stereotype = [k_st] if k_st
62
64
 
63
65
  k.attributes = create_uml_class_attributes(klass)
64
66
  k.associations = create_uml_associations(klass.id)
@@ -85,7 +87,8 @@ module Lutaml
85
87
  en.name = enum.name
86
88
  en.values = create_uml_values(enum)
87
89
  en.definition = doc_node_attribute_value(enum.id, "documentation")
88
- en.stereotype = doc_node_attribute_value(enum.id, "stereotype")
90
+ en_st = doc_node_attribute_value(enum.id, "stereotype")
91
+ en.stereotype = [en_st] if en_st
89
92
  end
90
93
  end
91
94
  end
@@ -107,9 +110,10 @@ module Lutaml
107
110
  data_type.definition = doc_node_attribute_value(
108
111
  dt.id, "documentation"
109
112
  )
110
- data_type.stereotype = doc_node_attribute_value(
113
+ dt_st = doc_node_attribute_value(
111
114
  dt.id, "stereotype"
112
115
  )
116
+ data_type.stereotype = [dt_st] if dt_st
113
117
 
114
118
  data_type.attributes = create_uml_class_attributes(dt)
115
119
  data_type.operations = create_uml_operations(dt)
@@ -246,7 +250,8 @@ module Lutaml
246
250
  gen.name = general_node.name
247
251
  gen.type = general_node.type
248
252
  gen.definition = lookup_element_prop_documentation(general_id)
249
- gen.stereotype = doc_node_attribute_value(general_id, "stereotype")
253
+ gen_st = doc_node_attribute_value(general_id, "stereotype")
254
+ gen.stereotype = [gen_st] if gen_st
250
255
 
251
256
  if next_general_node_id
252
257
  gen.general = set_uml_generalization(
@@ -339,8 +344,7 @@ module Lutaml
339
344
  end
340
345
 
341
346
  def create_uml_associations(xmi_id) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
342
- matched_element = @xmi_root_model.extension.elements.element
343
- .find { |e| e.idref == xmi_id }
347
+ matched_element = xmi_index&.find_element(xmi_id)
344
348
 
345
349
  return if !matched_element || !matched_element.links
346
350
 
@@ -250,7 +250,10 @@ module Lutaml
250
250
  # EA uses y-up convention; SVG uses y-down.
251
251
  # Also shifts all coordinates so minimum x,y is at padding offset.
252
252
  def normalize_coordinates(elements, connectors) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
253
- return { elements: elements, connectors: connectors } if elements.empty?
253
+ if elements.empty?
254
+ return { elements: elements,
255
+ connectors: connectors }
256
+ end
254
257
 
255
258
  padding = 10
256
259
 
@@ -388,11 +391,13 @@ module Lutaml
388
391
 
389
392
  # Add source/target positions
390
393
  if source_obj
391
- connector_data[:source_element] = diagram_object_bounds(source_obj)
394
+ connector_data[:source_element] =
395
+ diagram_object_bounds(source_obj)
392
396
  end
393
397
 
394
398
  if target_obj
395
- connector_data[:target_element] = diagram_object_bounds(target_obj)
399
+ connector_data[:target_element] =
400
+ diagram_object_bounds(target_obj)
396
401
  end
397
402
 
398
403
  # Add role and multiplicity
@@ -23,6 +23,9 @@ module Lutaml
23
23
  # @return [Hash] Parsing options
24
24
  attr_reader :options
25
25
 
26
+ # @return [Float, nil] Duration of last parse in seconds
27
+ attr_reader :last_duration
28
+
26
29
  # Initialize parser with configuration and options
27
30
  #
28
31
  # @param configuration [Configuration] Transformation configuration
@@ -32,6 +35,15 @@ module Lutaml
32
35
  @options = default_options.merge(options)
33
36
  @errors = []
34
37
  @warnings = []
38
+ @parse_stats = {
39
+ total_parses: 0,
40
+ successful_parses: 0,
41
+ failed_parses: 0,
42
+ total_duration: 0,
43
+ durations: [],
44
+ }
45
+ @last_duration = nil
46
+ @stats_mutex = Mutex.new
35
47
  end
36
48
 
37
49
  # Parse a model file into a UML document
@@ -44,25 +56,46 @@ module Lutaml
44
56
  # @return [Lutaml::Uml::Document] Parsed UML document
45
57
  # @raise [ParseError] if parsing fails
46
58
  def parse(file_path) # rubocop:disable Metrics/MethodLength
47
- validate_file!(file_path) if should_validate_input?
48
- clear_errors_and_warnings
59
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
60
+ @stats_mutex.synchronize { @parse_stats[:total_parses] += 1 }
61
+ parse_succeeded = false
62
+ parse_handled = false
49
63
 
50
64
  begin
51
- # Pre-parsing hook
52
- before_parse(file_path)
65
+ validate_file!(file_path) if should_validate_input?
66
+ clear_errors_and_warnings
67
+
68
+ begin
69
+ # Pre-parsing hook
70
+ before_parse(file_path)
53
71
 
54
- # Core parsing (implemented by subclasses)
55
- document = parse_internal(file_path)
72
+ # Core parsing (implemented by subclasses)
73
+ document = parse_internal(file_path)
56
74
 
57
- # Post-parsing processing
58
- document = after_parse(document, file_path)
75
+ # Post-parsing processing
76
+ document = after_parse(document, file_path)
59
77
 
60
- # Validate output if requested
61
- validate_output!(document) if should_validate_output?
78
+ # Validate output if requested
79
+ validate_output!(document) if should_validate_output?
62
80
 
63
- document
64
- rescue StandardError => e
65
- handle_parsing_error(e, file_path)
81
+ parse_succeeded = true
82
+ @stats_mutex.synchronize { @parse_stats[:successful_parses] += 1 }
83
+ document
84
+ rescue StandardError => e
85
+ parse_handled = true
86
+ @stats_mutex.synchronize { @parse_stats[:failed_parses] += 1 }
87
+ handle_parsing_error(e, file_path)
88
+ end
89
+ ensure
90
+ unless parse_succeeded || parse_handled
91
+ @stats_mutex.synchronize { @parse_stats[:failed_parses] += 1 }
92
+ end
93
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
94
+ @last_duration = duration
95
+ @stats_mutex.synchronize do
96
+ @parse_stats[:total_duration] += duration
97
+ @parse_stats[:durations] << duration
98
+ end
66
99
  end
67
100
  end
68
101
 
@@ -142,14 +175,38 @@ module Lutaml
142
175
  #
143
176
  # @return [Hash] Statistics about the parsing process
144
177
  def statistics
178
+ stats = @stats_mutex.synchronize { @parse_stats.dup }
179
+ durations = stats[:durations]
145
180
  {
146
181
  format: format_name,
147
182
  errors: @errors.size,
148
183
  warnings: @warnings.size,
149
184
  options: @options,
185
+ total_parses: stats[:total_parses],
186
+ successful_parses: stats[:successful_parses],
187
+ failed_parses: stats[:failed_parses],
188
+ success_rate: calculate_success_rate(stats),
189
+ average_duration: calculate_average_duration(durations),
190
+ total_duration: stats[:total_duration],
150
191
  }
151
192
  end
152
193
 
194
+ # Reset all parsing statistics
195
+ #
196
+ # @return [void]
197
+ def reset_statistics
198
+ @stats_mutex.synchronize do
199
+ @parse_stats = {
200
+ total_parses: 0,
201
+ successful_parses: 0,
202
+ failed_parses: 0,
203
+ total_duration: 0,
204
+ durations: [],
205
+ }
206
+ end
207
+ @last_duration = nil
208
+ end
209
+
153
210
  protected
154
211
 
155
212
  # Core parsing implementation
@@ -260,6 +317,26 @@ module Lutaml
260
317
 
261
318
  private
262
319
 
320
+ # Calculate success rate percentage
321
+ #
322
+ # @param stats [Hash] Stats hash
323
+ # @return [Float] Success rate as percentage
324
+ def calculate_success_rate(stats)
325
+ return 0.0 if stats[:total_parses].zero?
326
+
327
+ (stats[:successful_parses].to_f / stats[:total_parses]) * 100.0
328
+ end
329
+
330
+ # Calculate average parse duration
331
+ #
332
+ # @param durations [Array<Float>] List of parse durations
333
+ # @return [Float] Average duration
334
+ def calculate_average_duration(durations)
335
+ return 0.0 if durations.empty?
336
+
337
+ durations.sum / durations.size
338
+ end
339
+
263
340
  # Validate input file exists and is readable
264
341
  #
265
342
  # @param file_path [String] Path to validate
@@ -21,10 +21,11 @@ module Lutaml
21
21
  def transform(ea_object) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
22
22
  return nil if ea_object.nil?
23
23
 
24
- # Allow Class, Interface, and Text objects that appear on diagrams
24
+ # Allow Class, Interface, ProxyConnector, and Text objects
25
25
  is_class_type = ea_object.uml_class? || ea_object.interface?
26
+ is_proxy = ea_object.object_type == "ProxyConnector"
26
27
  is_text_class = ea_object.object_type == "Text"
27
- return nil unless is_class_type || is_text_class
28
+ return nil unless is_class_type || is_proxy || is_text_class
28
29
 
29
30
  Lutaml::Uml::Class.new.tap do |klass| # rubocop:disable Metrics/BlockLength
30
31
  # Map basic properties
@@ -33,7 +34,7 @@ module Lutaml
33
34
  "EAID")
34
35
  klass.is_abstract = ea_object.abstract?
35
36
  # Text objects exported as Class in XMI
36
- klass.type = is_text_class ? "Class" : "Class"
37
+ klass.type = "Class"
37
38
  klass.visibility = map_visibility(ea_object.visibility)
38
39
 
39
40
  # Map stereotype - return string if single, array if multiple
@@ -49,26 +50,14 @@ module Lutaml
49
50
  stereotypes << xref_stereotype
50
51
  end
51
52
 
52
- # Return string if single stereotype, array if multiple, nil if none
53
+ # Assign stereotypes (always as array for collection: true)
53
54
  unless stereotypes.empty?
54
- klass.stereotype = if stereotypes.size == 1
55
- stereotypes.first
56
- else
57
- stereotypes
58
- end
55
+ klass.stereotype = stereotypes
59
56
  end
60
57
 
61
58
  # Add "interface" stereotype if it's an interface
62
- if ea_object.interface?
63
- if klass.stereotype.nil?
64
- klass.stereotype = "interface"
65
- elsif klass.stereotype.is_a?(String)
66
- klass.stereotype = [klass.stereotype, "interface"].uniq
67
- elsif klass.stereotype.is_a?(Array)
68
- unless klass.stereotype.include?("interface")
69
- klass.stereotype << "interface"
70
- end
71
- end
59
+ if ea_object.interface? && !klass.stereotype.include?("interface")
60
+ klass.stereotype << "interface"
72
61
  end
73
62
 
74
63
  # Map definition/notes
@@ -29,14 +29,9 @@ module Lutaml
29
29
  data_type.type = "DataType"
30
30
  data_type.visibility = map_visibility(ea_object.visibility)
31
31
 
32
- # Set package path
33
- data_type.package_path = calculate_package_path(
34
- ea_object.package_id,
35
- )
36
-
37
32
  # Map stereotype
38
33
  if ea_object.stereotype && !ea_object.stereotype.empty?
39
- data_type.stereotype = ea_object.stereotype
34
+ data_type.stereotype = [ea_object.stereotype]
40
35
  end
41
36
 
42
37
  # Map definition/notes
@@ -44,9 +39,7 @@ module Lutaml
44
39
  ea_object.note.nil? || ea_object.note.empty?
45
40
 
46
41
  # Load and transform attributes
47
- attts = load_attributes(ea_object.ea_object_id)
48
- assoc_attts = load_association_attributes(ea_object.ea_object_id)
49
- data_type.attributes = attts + assoc_attts
42
+ data_type.attributes = load_attributes(ea_object.ea_object_id)
50
43
 
51
44
  # Load and transform operations
52
45
  data_type.operations = load_operations(ea_object.ea_object_id)
@@ -61,9 +61,6 @@ module Lutaml
61
61
 
62
62
  # Load diagram type
63
63
  diagram.diagram_type = ea_diagram.diagram_type
64
-
65
- # Load diagram id
66
- diagram.package_id = ea_diagram.package_id
67
64
  end
68
65
  end
69
66
 
@@ -15,7 +15,8 @@ module Lutaml
15
15
  # @return [Lutaml::Uml::Enum] UML enum
16
16
  def transform(ea_object) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
17
17
  return nil if ea_object.nil?
18
- return nil unless ea_object.enumeration?
18
+ return nil unless ea_object.enumeration? ||
19
+ (ea_object.stereotype && ea_object.stereotype.downcase == "enumeration")
19
20
 
20
21
  Lutaml::Uml::Enum.new.tap do |enum|
21
22
  # Map basic properties
@@ -24,12 +25,9 @@ module Lutaml
24
25
  "EAID")
25
26
  enum.visibility = map_visibility(ea_object.visibility)
26
27
 
27
- # Set package path
28
- enum.package_path = calculate_package_path(ea_object.package_id)
29
-
30
28
  # Map stereotype
31
29
  if ea_object.stereotype && !ea_object.stereotype.empty?
32
- enum.stereotype = ea_object.stereotype
30
+ enum.stereotype = [ea_object.stereotype]
33
31
  end
34
32
 
35
33
  # Map definition/notes
@@ -38,7 +38,7 @@ module Lutaml
38
38
  end
39
39
 
40
40
  # Map stereotype from current object
41
- gen.stereotype = current_object.stereotype unless
41
+ gen.stereotype = [current_object.stereotype] unless
42
42
  current_object.stereotype.nil? || current_object.stereotype.empty?
43
43
 
44
44
  # Find the package/upper class for the current object