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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +73 -31
- data/Gemfile +2 -5
- data/TODO.bugfix.md +24 -0
- data/lib/lutaml/cli/enhanced_formatter.rb +4 -2
- data/lib/lutaml/cli/tree_view_formatter.rb +1 -1
- data/lib/lutaml/cli/uml/diagram_command.rb +2 -6
- data/lib/lutaml/converter/xmi_to_uml.rb +11 -7
- data/lib/lutaml/ea/diagram/extractor.rb +8 -3
- data/lib/lutaml/model_transformations/parsers/base_parser.rb +90 -13
- data/lib/lutaml/qea/factory/class_transformer.rb +8 -19
- data/lib/lutaml/qea/factory/data_type_transformer.rb +2 -9
- data/lib/lutaml/qea/factory/diagram_transformer.rb +0 -3
- data/lib/lutaml/qea/factory/enum_transformer.rb +3 -5
- data/lib/lutaml/qea/factory/generalization_transformer.rb +1 -1
- data/lib/lutaml/qea/factory/package_transformer.rb +45 -19
- data/lib/lutaml/qea/validation/base_validator.rb +5 -0
- data/lib/lutaml/qea/verification/document_normalizer.rb +4 -12
- data/lib/lutaml/qea/verification/document_verifier.rb +25 -15
- data/lib/lutaml/uml_repository/index_builder.rb +27 -9
- data/lib/lutaml/uml_repository/presenters/diagram_presenter.rb +2 -7
- data/lib/lutaml/uml_repository/queries/inheritance_query.rb +66 -0
- data/lib/lutaml/uml_repository/queries/search_query.rb +1 -1
- data/lib/lutaml/uml_repository/repository.rb +34 -0
- data/lib/lutaml/uml_repository/validators/repository_validator.rb +1 -1
- data/lib/lutaml/uml_repository/web_ui/app.rb +25 -7
- data/lib/lutaml/version.rb +1 -1
- data/lib/lutaml/xmi/parsers/xmi_base.rb +67 -150
- data/lutaml.gemspec +3 -0
- metadata +45 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 380893deae7b963ad1177e3cec77bcb1752dfb15efd8560a952535e58bdec608
|
|
4
|
+
data.tar.gz: cab5e523724906b6a9ff202a335195c73b7762521acc8550705b1fae66784edf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
277
|
+
# Offense count: 943
|
|
217
278
|
RSpec/MultipleExpectations:
|
|
218
279
|
Max: 26
|
|
219
280
|
|
|
220
|
-
# Offense count:
|
|
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:
|
|
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 "
|
|
12
|
-
gem "
|
|
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
|
-
|
|
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] =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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] =
|
|
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] =
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
55
|
-
|
|
72
|
+
# Core parsing (implemented by subclasses)
|
|
73
|
+
document = parse_internal(file_path)
|
|
56
74
|
|
|
57
|
-
|
|
58
|
-
|
|
75
|
+
# Post-parsing processing
|
|
76
|
+
document = after_parse(document, file_path)
|
|
59
77
|
|
|
60
|
-
|
|
61
|
-
|
|
78
|
+
# Validate output if requested
|
|
79
|
+
validate_output!(document) if should_validate_output?
|
|
62
80
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
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 =
|
|
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
|
-
#
|
|
53
|
+
# Assign stereotypes (always as array for collection: true)
|
|
53
54
|
unless stereotypes.empty?
|
|
54
|
-
klass.stereotype =
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -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
|