xmi 0.5.2 → 0.5.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ac4f03d86ac8abffeaecaf956bcd77ab9a3bb2ed6348a12f3ccb2bd587f2eec
4
- data.tar.gz: 7f0051b7bbb867c4c7d82ad95fb435a4f5e5d9bb2b76e1297ded8953682489c1
3
+ metadata.gz: ea334f95069f7cc7ba3116b4bac7e57bfe56476c6803dd59c84b458b0f9549fc
4
+ data.tar.gz: a2c61004fa041d2956bbad2f141750ce7c654b552a20ada2040e68e495559f07
5
5
  SHA512:
6
- metadata.gz: 99a98d34c015c29e2170695e0e3918a98ec9a4308097a32886bd0af401808172b367c2e2da020713dc0ff2db5581c9336b4a4a64e3d3b39b225f4df5cde0296d
7
- data.tar.gz: 060447e7ae2275a40e4881616dc104032c491ae4cc3bb0707bcfbcafb7bf93a068ff6cda7da3cbeb62c37c3ef5d8e34f66a1dfd1a11206b509e99d3af27a7d8e
6
+ metadata.gz: 6c26a6117f49257ec68b0934783cbae29265f79dce927232df36737af3c1076b9802d96d02bf864d76841c820a8ce03869a92af64f31405b6d00b508795b4eff
7
+ data.tar.gz: 88b6d2d73963616ce39ce6517cb7f2d0a6c3bd86d46e4d0c52a4463e6a2d1c2d6923863c174afac74205619d2a9d30f5edec2adc58c654915a3f361f85c635d5
@@ -9,6 +9,10 @@ on:
9
9
 
10
10
  jobs:
11
11
  rake:
12
- uses: metanorma/ci/.github/workflows/graphviz-rake.yml@main
12
+ uses: metanorma/ci/.github/workflows/generic-rake.yml@main
13
+ with:
14
+ setup-tools: graphviz
15
+ submodules: true
16
+ choco-cache: true
13
17
  secrets:
14
18
  pat_token: ${{ secrets.METANORMA_CI_PAT_TOKEN }}
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-04-14 03:59:00 UTC using RuboCop version 1.86.1.
3
+ # on 2026-04-18 04:57:18 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
@@ -11,13 +11,28 @@ Gemspec/RequiredRubyVersion:
11
11
  Exclude:
12
12
  - 'xmi.gemspec'
13
13
 
14
- # Offense count: 72
14
+ # Offense count: 1
15
+ # This cop supports safe autocorrection (--autocorrect).
16
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
17
+ # SupportedStyles: with_first_argument, with_fixed_indentation
18
+ Layout/ArgumentAlignment:
19
+ Exclude:
20
+ - 'lib/xmi/index.rb'
21
+
22
+ # Offense count: 73
15
23
  # This cop supports safe autocorrection (--autocorrect).
16
24
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
17
25
  # URISchemes: http, https
18
26
  Layout/LineLength:
19
27
  Enabled: false
20
28
 
29
+ # Offense count: 1
30
+ # This cop supports safe autocorrection (--autocorrect).
31
+ # Configuration parameters: AllowInHeredoc.
32
+ Layout/TrailingWhitespace:
33
+ Exclude:
34
+ - 'lib/xmi/index.rb'
35
+
21
36
  # Offense count: 5
22
37
  # Configuration parameters: AllowedMethods.
23
38
  # AllowedMethods: enums
@@ -25,7 +40,16 @@ Lint/ConstantDefinitionInBlock:
25
40
  Exclude:
26
41
  - 'spec/performance/xmi_parsing_spec.rb'
27
42
 
28
- # Offense count: 12
43
+ # Offense count: 1
44
+ # This cop supports unsafe autocorrection (--autocorrect-all).
45
+ # Configuration parameters: AllowedMethods, InferNonNilReceiver, AdditionalNilMethods.
46
+ # AllowedMethods: instance_of?, kind_of?, is_a?, eql?, respond_to?, equal?
47
+ # AdditionalNilMethods: present?, blank?, try, try!
48
+ Lint/RedundantSafeNavigation:
49
+ Exclude:
50
+ - 'lib/xmi/index.rb'
51
+
52
+ # Offense count: 14
29
53
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
30
54
  Metrics/AbcSize:
31
55
  Exclude:
@@ -33,6 +57,7 @@ Metrics/AbcSize:
33
57
  - 'lib/tasks/performance_comparator.rb'
34
58
  - 'lib/tasks/performance_helpers.rb'
35
59
  - 'lib/xmi/ea_root.rb'
60
+ - 'lib/xmi/index.rb'
36
61
  - 'lib/xmi/version_registry.rb'
37
62
  - 'spec/performance/xmi_parsing_spec.rb'
38
63
 
@@ -42,24 +67,33 @@ Metrics/AbcSize:
42
67
  Metrics/BlockLength:
43
68
  Max: 143
44
69
 
45
- # Offense count: 1
70
+ # Offense count: 4
46
71
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
47
72
  Metrics/CyclomaticComplexity:
48
73
  Exclude:
49
74
  - 'lib/tasks/performance_helpers.rb'
75
+ - 'lib/xmi/index.rb'
50
76
 
51
- # Offense count: 26
77
+ # Offense count: 28
52
78
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
53
79
  Metrics/MethodLength:
54
80
  Max: 33
55
81
 
56
- # Offense count: 2
82
+ # Offense count: 4
57
83
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
58
84
  Metrics/PerceivedComplexity:
59
85
  Exclude:
60
86
  - 'lib/tasks/performance_helpers.rb'
87
+ - 'lib/xmi/index.rb'
61
88
  - 'lib/xmi/version_registry.rb'
62
89
 
90
+ # Offense count: 3
91
+ # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
92
+ # AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to
93
+ Naming/MethodParameterName:
94
+ Exclude:
95
+ - 'lib/xmi/index.rb'
96
+
63
97
  # Offense count: 19
64
98
  # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
65
99
  # SupportedStyles: snake_case, normalcase, non_integer
@@ -122,6 +156,12 @@ RSpec/MultipleMemoizedHelpers:
122
156
  RSpec/NestedGroups:
123
157
  Max: 4
124
158
 
159
+ # Offense count: 1
160
+ # This cop supports safe autocorrection (--autocorrect).
161
+ Style/MultilineIfModifier:
162
+ Exclude:
163
+ - 'lib/xmi/index.rb'
164
+
125
165
  # Offense count: 4
126
166
  # Configuration parameters: AllowedClasses.
127
167
  Style/OneClassPerFile:
data/lib/xmi/index.rb ADDED
@@ -0,0 +1,243 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ # Builds all commonly needed indexes from a parsed SparxRoot in a single
5
+ # targeted walk, avoiding the generic map_id_name approach that visits every
6
+ # attribute of every node.
7
+ #
8
+ # Indexes built:
9
+ # - id_name_map: { xmi_id => name } for all nodes with both id and name
10
+ # - packaged_elements: flat array of all PackagedElement instances
11
+ # - packaged_by_id: { xmi_id => PackagedElement }
12
+ # - packaged_by_type: { "uml:Class" => [...], "uml:Package" => [...], ... }
13
+ # - upper_level_map: { xmi_id => parent PackagedElement }
14
+ # - elements_by_idref: { idref => Extension::Element::Element }
15
+ # - connectors_by_idref: { idref => Connector::Connector }
16
+ # - attributes_by_idref: { idref => Extension::Element::Attribute }
17
+ # - owned_attrs_by_type_idref: { type_idref => [OwnedAttribute, ...] }
18
+ class Index
19
+ attr_reader :id_name_map, :packaged_elements, :packaged_by_id,
20
+ :packaged_by_type, :upper_level_map, :elements_by_idref,
21
+ :connectors_by_idref, :attributes_by_idref,
22
+ :owned_attrs_by_type_idref
23
+
24
+ PackagedElement = ::Xmi::Uml::PackagedElement
25
+
26
+ # @param root [Xmi::Sparx::SparxRoot] parsed XMI model
27
+ def initialize(root)
28
+ @id_name_map = {}
29
+ @packaged_elements = []
30
+ @packaged_by_id = {}
31
+ @packaged_by_type = {}
32
+ @upper_level_map = {}
33
+ @elements_by_idref = {}
34
+ @connectors_by_idref = {}
35
+ @attributes_by_idref = {}
36
+ @owned_attrs_by_type_idref = {}
37
+
38
+ build(root)
39
+ freeze
40
+ end
41
+
42
+ # Lookup name by XMI ID
43
+ # @param xmi_id [String] XMI identifier
44
+ # @return [String, nil]
45
+ def lookup_name(xmi_id)
46
+ @id_name_map[xmi_id]
47
+ end
48
+
49
+ # Find packaged element by XMI ID
50
+ # @param id [String]
51
+ # @return [Xmi::Uml::PackagedElement, nil]
52
+ def find_packaged_element(id)
53
+ @packaged_by_id[id]
54
+ end
55
+
56
+ # Find parent of a packaged element
57
+ # @param id [String] child XMI ID
58
+ # @return [Xmi::Uml::PackagedElement, nil]
59
+ def find_parent(id)
60
+ @upper_level_map[id]
61
+ end
62
+
63
+ # Find extension element by idref
64
+ # @param idref [String]
65
+ # @return [Xmi::Sparx::Element::Element, nil]
66
+ def find_element(idref)
67
+ @elements_by_idref[idref]
68
+ end
69
+
70
+ # Find connector by idref
71
+ # @param idref [String]
72
+ # @return [Xmi::Sparx::Connector::Connector, nil]
73
+ def find_connector(idref)
74
+ @connectors_by_idref[idref]
75
+ end
76
+
77
+ # Find extension attribute by idref
78
+ # @param idref [String]
79
+ # @return [Xmi::Sparx::Element::Attribute, nil]
80
+ def find_attribute(idref)
81
+ @attributes_by_idref[idref]
82
+ end
83
+
84
+ # Find owned attributes whose uml_type.idref matches
85
+ # @param type_idref [String]
86
+ # @return [Array<Xmi::Uml::OwnedAttribute>]
87
+ def find_owned_attrs_by_type(type_idref)
88
+ @owned_attrs_by_type_idref[type_idref] || []
89
+ end
90
+
91
+ # Find packaged elements by UML type
92
+ # @param type [String] e.g. "uml:Class", "uml:Package"
93
+ # @return [Array<Xmi::Uml::PackagedElement>]
94
+ def packaged_elements_of_type(type)
95
+ @packaged_by_type[type] || []
96
+ end
97
+
98
+ # Find first packaged element by name and allowed types
99
+ # @param name [String]
100
+ # @param types [Array<String>] e.g. ["uml:Class", "uml:AssociationClass"]
101
+ # @return [Xmi::Uml::PackagedElement, nil]
102
+ def find_packaged_by_name_and_types(name, types)
103
+ types.each do |type|
104
+ elements = @packaged_by_type[type]
105
+ next unless elements
106
+
107
+ found = elements.find { |e| e.name == name }
108
+ return found if found
109
+ end
110
+ nil
111
+ end
112
+
113
+ private
114
+
115
+ def build(root)
116
+ model = root.model
117
+ if model
118
+ id = model.id if model.respond_to?(:id)
119
+ @id_name_map[id] = model.name if id && model.name
120
+ walk_packaged_elements(model.packaged_element, nil)
121
+ end
122
+
123
+ ext = root.extension
124
+ return unless ext
125
+
126
+ index_extension_elements(ext.elements)
127
+ index_extension_connectors(ext.connectors)
128
+
129
+ primitives = ext.primitive_types
130
+ walk_packaged_elements(primitives.packaged_element, nil) if primitives
131
+
132
+ index_extension_profiles(ext.profiles)
133
+ end
134
+
135
+ def walk_packaged_elements(elements, parent) # rubocop:disable Metrics/MethodLength
136
+ return unless elements
137
+
138
+ id_name_map = @id_name_map
139
+ packaged_by_id = @packaged_by_id
140
+ packaged_by_type = @packaged_by_type
141
+ upper_level_map = @upper_level_map
142
+ packaged_elements = @packaged_elements
143
+ owned_attrs_by_type = @owned_attrs_by_type_idref
144
+
145
+ elements.each do |pe|
146
+ next unless pe.is_a?(PackagedElement)
147
+
148
+ pe_id = pe.id
149
+ pe_type = pe.type
150
+ pe_name = pe.name
151
+
152
+ packaged_elements << pe
153
+ packaged_by_id[pe_id] = pe if pe_id
154
+ (packaged_by_type[pe_type] ||= []) << pe if pe_type
155
+ upper_level_map[pe_id] = parent if parent && pe_id
156
+ id_name_map[pe_id] = pe_name if pe_id && pe_name
157
+
158
+ # Inline owned attribute indexing
159
+ attrs = pe.owned_attribute
160
+ if attrs && !attrs.empty?
161
+ attrs.each do |oa|
162
+ oa_id = oa.id
163
+ id_name_map[oa_id] = oa.name if oa_id && oa.name
164
+ oa_type = oa.uml_type
165
+ if oa.association && oa_type && oa_type.idref
166
+ (owned_attrs_by_type[oa_type.idref] ||= []) << oa
167
+ end
168
+ end
169
+ end
170
+
171
+ # Inline owned operation indexing
172
+ ops = pe.owned_operation
173
+ if ops && !ops.empty?
174
+ ops.each do |oo|
175
+ oo_id = oo.id
176
+ id_name_map[oo_id] = oo.name if oo_id && oo.name
177
+ end
178
+ end
179
+
180
+ # Inline owned literal indexing
181
+ lits = pe.owned_literal
182
+ if lits && !lits.empty?
183
+ lits.each do |ol|
184
+ ol_id = ol.id
185
+ id_name_map[ol_id] = ol.name if ol_id && ol.name
186
+ end
187
+ end
188
+
189
+ # Recurse into children
190
+ walk_packaged_elements(pe.packaged_element, pe)
191
+ end
192
+ end
193
+
194
+ def index_extension_elements(elements)
195
+ return unless elements
196
+
197
+ element_list = elements.element
198
+ return unless element_list
199
+
200
+ element_list.each do |e|
201
+ idref = e.idref
202
+ next unless idref
203
+
204
+ @elements_by_idref[idref] = e
205
+
206
+ props = e.properties
207
+ @id_name_map[idref] = props.name if props&.name
208
+
209
+ attrs_obj = e.attributes
210
+ next unless attrs_obj
211
+
212
+ attr_list = attrs_obj.attribute
213
+ next unless attr_list
214
+
215
+ attr_list.each do |a|
216
+ @attributes_by_idref[a.idref] = a if a.idref
217
+ end
218
+ end
219
+ end
220
+
221
+ def index_extension_connectors(connectors)
222
+ return unless connectors
223
+
224
+ conn_list = connectors.connector
225
+ return unless conn_list
226
+
227
+ conn_list.each do |c|
228
+ @connectors_by_idref[c.idref] = c if c.idref
229
+ end
230
+ end
231
+
232
+ def index_extension_profiles(profiles)
233
+ return unless profiles
234
+
235
+ profile_list = profiles.profile
236
+ return unless profile_list
237
+
238
+ profile_list.each do |profile|
239
+ walk_packaged_elements(profile.packaged_element, nil) if profile.respond_to?(:packaged_element)
240
+ end
241
+ end
242
+ end
243
+ end
@@ -27,13 +27,15 @@ module Xmi
27
27
  # their corresponding UML versions.
28
28
  #
29
29
  # @param xml_content [String] The raw XMI XML content
30
- # @return [SparxRoot] The parsed Ruby object
30
+ # @return [SparxRoot] The parsed Ruby object with index built
31
31
  #
32
32
  # @see Xmi.parse
33
33
  def parse_xml(xml_content)
34
34
  xml_content = fix_encoding(xml_content)
35
35
  Xmi.init_versioning!
36
- Xmi::VersionRegistry.parse_with_detected_version(xml_content, self)
36
+ root = Xmi::VersionRegistry.parse_with_detected_version(xml_content, self)
37
+ root.build_index
38
+ root
37
39
  end
38
40
 
39
41
  # Fix invalid UTF-8 encoding in the XML content.
@@ -55,6 +57,18 @@ module Xmi
55
57
 
56
58
  # Use the reusable BaseMapping class instead of eval hack
57
59
  xml SparxMappings::BaseMapping
60
+
61
+ # Build index for fast lookups
62
+ # @return [Xmi::Index]
63
+ def build_index
64
+ @index = Xmi::Index.new(self)
65
+ end
66
+
67
+ # Access the index (builds on first access if needed)
68
+ # @return [Xmi::Index]
69
+ def index
70
+ @index || build_index
71
+ end
58
72
  end
59
73
  end
60
74
  end
data/lib/xmi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Xmi
4
- VERSION = "0.5.2"
4
+ VERSION = "0.5.3"
5
5
  end
@@ -158,7 +158,8 @@ module Xmi
158
158
  # Extend fallback chain only if it won't create a cycle.
159
159
  # A cycle would occur if reg's fallback chain includes primary_register.
160
160
  # We check this by seeing if primary_register.id appears in reg's fallback.
161
- primary_register.fallback << reg.id unless reg.fallback.include?(primary_register.id)
161
+ # Use add_fallback to keep Register and TypeContext in sync and invalidate caches.
162
+ primary_register.add_fallback(reg.id) unless reg.fallback.include?(primary_register.id)
162
163
  end
163
164
  end
164
165
  # rubocop:enable Metrics/CyclomaticComplexity
data/lib/xmi.rb CHANGED
@@ -55,6 +55,7 @@ require_relative "xmi/uml"
55
55
  require_relative "xmi/custom_profile"
56
56
  require_relative "xmi/root"
57
57
  require_relative "xmi/sparx"
58
+ require_relative "xmi/index"
58
59
 
59
60
  # Unified parsing API
60
61
  require_relative "xmi/parsing"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xmi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-14 00:00:00.000000000 Z
11
+ date: 2026-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model
@@ -72,6 +72,7 @@ files:
72
72
  - lib/xmi/documentation.rb
73
73
  - lib/xmi/ea_root.rb
74
74
  - lib/xmi/extension.rb
75
+ - lib/xmi/index.rb
75
76
  - lib/xmi/namespace.rb
76
77
  - lib/xmi/namespace/dynamic.rb
77
78
  - lib/xmi/namespace/omg.rb