xmi 0.5.3 → 0.5.5

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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +31 -45
  3. data/README.adoc +0 -15
  4. data/lib/xmi/custom_profile/abstract.rb +16 -0
  5. data/lib/xmi/custom_profile/basic_doc.rb +16 -0
  6. data/lib/xmi/custom_profile/bibliography.rb +16 -0
  7. data/lib/xmi/custom_profile/edition.rb +18 -0
  8. data/lib/xmi/custom_profile/enumeration.rb +16 -0
  9. data/lib/xmi/custom_profile/informative.rb +16 -0
  10. data/lib/xmi/custom_profile/invariant.rb +16 -0
  11. data/lib/xmi/custom_profile/number.rb +18 -0
  12. data/lib/xmi/custom_profile/ocl.rb +16 -0
  13. data/lib/xmi/custom_profile/persistence.rb +20 -0
  14. data/lib/xmi/custom_profile/publication_date.rb +18 -0
  15. data/lib/xmi/custom_profile/year_version.rb +18 -0
  16. data/lib/xmi/custom_profile.rb +12 -143
  17. data/lib/xmi/ea_root/code_generation.rb +141 -0
  18. data/lib/xmi/ea_root/extension_lifecycle.rb +52 -0
  19. data/lib/xmi/ea_root/namespace_handling.rb +39 -0
  20. data/lib/xmi/ea_root/xml_parsing.rb +51 -0
  21. data/lib/xmi/ea_root.rb +16 -403
  22. data/lib/xmi/extension.rb +4 -12
  23. data/lib/xmi/parser_pipeline.rb +70 -0
  24. data/lib/xmi/root.rb +3 -12
  25. data/lib/xmi/sparx/connector/appearance.rb +27 -0
  26. data/lib/xmi/sparx/connector/connector.rb +52 -0
  27. data/lib/xmi/sparx/connector/end_base.rb +56 -0
  28. data/lib/xmi/sparx/connector/end_constraint.rb +33 -0
  29. data/lib/xmi/sparx/connector/end_modifiers.rb +19 -0
  30. data/lib/xmi/sparx/connector/end_role.rb +21 -0
  31. data/lib/xmi/sparx/connector/end_style.rb +17 -0
  32. data/lib/xmi/sparx/connector/end_type.rb +21 -0
  33. data/lib/xmi/sparx/connector/labels.rb +27 -0
  34. data/lib/xmi/sparx/connector/model.rb +19 -0
  35. data/lib/xmi/sparx/connector/properties.rb +19 -0
  36. data/lib/xmi/sparx/connector.rb +15 -233
  37. data/lib/xmi/sparx/element/association.rb +58 -0
  38. data/lib/xmi/sparx/element/attribute.rb +58 -0
  39. data/lib/xmi/sparx/element/bounds.rb +19 -0
  40. data/lib/xmi/sparx/element/code.rb +19 -0
  41. data/lib/xmi/sparx/element/containment.rb +19 -0
  42. data/lib/xmi/sparx/element/coords.rb +19 -0
  43. data/lib/xmi/sparx/element/documentation.rb +17 -0
  44. data/lib/xmi/sparx/element/element.rb +62 -0
  45. data/lib/xmi/sparx/element/extended_properties.rb +21 -0
  46. data/lib/xmi/sparx/element/flags.rb +29 -0
  47. data/lib/xmi/sparx/element/links.rb +22 -0
  48. data/lib/xmi/sparx/element/model.rb +26 -0
  49. data/lib/xmi/sparx/element/package_properties.rb +21 -0
  50. data/lib/xmi/sparx/element/paths.rb +17 -0
  51. data/lib/xmi/sparx/element/project.rb +29 -0
  52. data/lib/xmi/sparx/element/properties.rb +41 -0
  53. data/lib/xmi/sparx/element/stereotype.rb +17 -0
  54. data/lib/xmi/sparx/element/style.rb +17 -0
  55. data/lib/xmi/sparx/element/styleex.rb +17 -0
  56. data/lib/xmi/sparx/element/tag.rb +32 -0
  57. data/lib/xmi/sparx/element/times.rb +23 -0
  58. data/lib/xmi/sparx/element/xrefs.rb +17 -0
  59. data/lib/xmi/sparx/element.rb +28 -445
  60. data/lib/xmi/sparx/extension.rb +4 -12
  61. data/lib/xmi/sparx/gml/application_schema.rb +26 -0
  62. data/lib/xmi/sparx/gml/code_list.rb +23 -0
  63. data/lib/xmi/sparx/gml/data_type.rb +18 -0
  64. data/lib/xmi/sparx/gml/enumeration.rb +18 -0
  65. data/lib/xmi/sparx/gml/feature_type.rb +21 -0
  66. data/lib/xmi/sparx/gml/property.rb +24 -0
  67. data/lib/xmi/sparx/gml/shared_attributes.rb +39 -0
  68. data/lib/xmi/sparx/gml/type.rb +18 -0
  69. data/lib/xmi/sparx/gml/union.rb +18 -0
  70. data/lib/xmi/sparx/gml.rb +10 -125
  71. data/lib/xmi/sparx/index.rb +248 -0
  72. data/lib/xmi/sparx/mappings/base_mapping.rb +13 -137
  73. data/lib/xmi/sparx/root.rb +10 -11
  74. data/lib/xmi/sparx.rb +1 -0
  75. data/lib/xmi/uml/annotated_element.rb +16 -0
  76. data/lib/xmi/uml/association_generalization.rb +20 -0
  77. data/lib/xmi/uml/bounds.rb +43 -0
  78. data/lib/xmi/uml/default_value.rb +42 -0
  79. data/lib/xmi/uml/diagram.rb +25 -0
  80. data/lib/xmi/uml/member_end.rb +16 -0
  81. data/lib/xmi/uml/owned_attribute.rb +31 -0
  82. data/lib/xmi/uml/owned_comment.rb +29 -0
  83. data/lib/xmi/uml/owned_element.rb +33 -0
  84. data/lib/xmi/uml/owned_end.rb +34 -0
  85. data/lib/xmi/uml/owned_literal.rb +23 -0
  86. data/lib/xmi/uml/owned_operation.rb +22 -0
  87. data/lib/xmi/uml/owned_parameter.rb +29 -0
  88. data/lib/xmi/uml/package_import.rb +30 -0
  89. data/lib/xmi/uml/packaged_element.rb +49 -0
  90. data/lib/xmi/uml/precondition.rb +22 -0
  91. data/lib/xmi/uml/profile.rb +43 -0
  92. data/lib/xmi/uml/profile_application.rb +34 -0
  93. data/lib/xmi/uml/specification.rb +20 -0
  94. data/lib/xmi/uml/type.rb +18 -0
  95. data/lib/xmi/uml/uml_model.rb +29 -0
  96. data/lib/xmi/uml.rb +27 -502
  97. data/lib/xmi/version.rb +1 -1
  98. data/lib/xmi/xmi_identity.rb +46 -0
  99. data/lib/xmi.rb +11 -7
  100. metadata +84 -3
  101. data/lib/xmi/index.rb +0 -243
data/lib/xmi/ea_root.rb CHANGED
@@ -1,58 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "nokogiri"
4
+ require_relative "ea_root/xml_parsing"
5
+ require_relative "ea_root/code_generation"
6
+ require_relative "ea_root/extension_lifecycle"
7
+ require_relative "ea_root/namespace_handling"
4
8
 
5
9
  module Xmi
6
- class EaRoot # rubocop:disable Metrics/ClassLength
7
- MODULE_TEMPLATE = <<~TEXT
8
- module Xmi
9
- class EaRoot
10
- module #MODULE_NAME#
11
- #KLASSES#
12
- end
13
- end
14
- end
15
- TEXT
16
-
17
- KLASS_TEMPLATE = <<~TEXT
18
- class #KLASS_NAME# < #FROM_KLASS#
19
- #ROOT_TAG_LINE#
20
-
21
- #ATTRIBUTES##XML_MAPPING#
22
- end
23
- TEXT
10
+ class EaRoot
11
+ extend XmlParsing
12
+ extend CodeGeneration
13
+ extend ExtensionLifecycle
14
+ extend NamespaceHandling
24
15
 
25
- XML_MAPPING = <<~TEXT
26
- xml do
27
- root "#ROOT_TAG#"
28
- namespace #NAMESPACE_CLASS#
29
- #MAP_ATTRIBUTES#
30
- end
31
- TEXT
32
-
33
- ATTRIBUTE_LINE = <<~TEXT
34
- attribute :#TAG_NAME#, #ATTRIBUTE_TYPE#
35
- TEXT
36
-
37
- MAP_ATTRIBUTES = <<~TEXT
38
- map_attribute "#ATTRIBUTE_NAME#", to: :#ATTRIBUTE_METHOD#
39
- TEXT
40
-
41
- MAP_ELEMENT = <<~TEXT
42
- map_element "#ELEMENT_NAME#",
43
- to: :#ELEMENT_METHOD#,
44
- value_map: Xmi::VALUE_MAP
45
- TEXT
16
+ private_constant :XmlParsing, :CodeGeneration, :ExtensionLifecycle,
17
+ :NamespaceHandling
46
18
 
47
19
  class << self
48
- # Load an EA extension from an XML file.
49
- #
50
- # @param xml_path [String] Path to the MDG extension XML file
51
- # @return [void]
52
- # @raise [ArgumentError] If the extension is already loaded
53
20
  def load_extension(xml_path)
54
- @content = gen_content(xml_path)
55
- extension_id = @module_name
21
+ extension_id = derive_module_name(xml_path)
56
22
 
57
23
  if loaded_extensions.key?(extension_id)
58
24
  raise ArgumentError,
@@ -61,18 +27,11 @@ module Xmi
61
27
  "Call unload_extension('#{extension_id}') first if you want to reload it."
62
28
  end
63
29
 
64
- Object.class_eval @content
30
+ build_extension(xml_path)
65
31
  update_mappings(extension_id)
66
32
  loaded_extensions[extension_id] = xml_path
67
33
  end
68
34
 
69
- # Unload an extension by removing its module and clearing tracking.
70
- #
71
- # This allows the extension to be loaded again, which is useful for
72
- # testing scenarios that need to reload extensions.
73
- #
74
- # @param extension_id [String, Symbol] The extension module name (e.g., "Citygml")
75
- # @return [void]
76
35
  def unload_extension(extension_id)
77
36
  extension_id = extension_id.to_s.capitalize
78
37
 
@@ -81,366 +40,20 @@ module Xmi
81
40
  loaded_extensions.delete(extension_id)
82
41
  end
83
42
 
84
- # Check if an extension is currently loaded.
85
- #
86
- # @param extension_id [String, Symbol] The extension module name
87
- # @return [Boolean]
88
43
  def extension_loaded?(extension_id)
89
44
  extension_id = extension_id.to_s.capitalize
90
45
  loaded_extensions.key?(extension_id)
91
46
  end
92
47
 
93
- # List all currently loaded extensions.
94
- #
95
- # @return [Hash<String, String>] Map of extension_id => xml_path
96
48
  def loaded_extensions
97
49
  @loaded_extensions ||= {}
98
50
  end
99
51
 
100
- def output_rb_file(output_rb_path)
101
- File.write(output_rb_path, @content)
102
- end
103
-
104
52
  private
105
53
 
106
- def update_mappings(module_name)
107
- new_klasses = all_new_klasses(module_name)
108
- map_elements = construct_xml_mappings(new_klasses, module_name)
109
- update_model_attributes(new_klasses, Xmi::Sparx::SparxRoot, module_name)
110
- update_model_xml_mappings(map_elements, Xmi::Sparx::SparxRoot)
111
- end
112
-
113
- def construct_xml_mappings(new_klasses, module_name)
114
- map_elements = []
115
- new_klasses.each do |klass|
116
- next unless Xmi::EaRoot.const_get(module_name).const_get(klass)
117
- .respond_to? :root_tag
118
-
119
- map_elements << MAP_ELEMENT
120
- .gsub("#ELEMENT_NAME#", Xmi::EaRoot.const_get(module_name).const_get(klass).root_tag)
121
- .gsub("#ELEMENT_METHOD#", Lutaml::Model::Utils.snake_case(klass.to_s))
122
- .gsub("#NAMESPACE#", @def_namespace[:uri])
123
- .gsub("#PREFIX#", @def_namespace[:name])
124
- end
125
-
126
- map_elements
127
- end
128
-
129
- def update_model_attributes(new_klasses, sparx_root, module_name)
130
- new_klasses.each do |klass|
131
- method_name = Lutaml::Model::Utils.snake_case(klass)
132
- full_klass_name = "Xmi::EaRoot::#{module_name}::#{klass}"
133
- attr_line = "#{ATTRIBUTE_LINE.rstrip}, collection: true"
134
- attr_line = attr_line
135
- .gsub("#TAG_NAME#", method_name)
136
- .gsub("#ATTRIBUTE_TYPE#", full_klass_name)
137
-
138
- sparx_root.class_eval(attr_line)
139
- end
140
- end
141
-
142
- # Add extension element mappings to SparxRoot.
143
- #
144
- # Only adds NEW mappings for the extension elements. Does NOT re-evaluate
145
- # the base mappings, which avoids duplicate mapping accumulation.
146
- #
147
- # @param map_elements [Array<String>] Array of map_element code strings
148
- # @param sparx_root [Class] The SparxRoot class to update
149
- def update_model_xml_mappings(map_elements, sparx_root)
150
- return if map_elements.empty?
151
-
152
- # Only add the NEW extension element mappings.
153
- # Do NOT re-evaluate base mappings to avoid duplicates.
154
- extension_block = proc do
155
- map_elements.each { |element_code| instance_eval(element_code) }
156
- end
157
- sparx_root.class_eval { xml(&extension_block) }
158
- end
159
-
160
- def all_new_klasses(module_name)
161
- Xmi::EaRoot.const_get(module_name).constants.select do |c|
162
- Xmi::EaRoot.const_get(module_name).const_get(c).is_a? Class
163
- end
164
- end
165
-
166
- def get_abstract_klass_node(xmi_doc)
167
- xmi_doc.at_xpath(
168
- "//UMLProfiles//Stereotypes//Stereotype[@isAbstract='true']",
169
- )
170
- end
171
-
172
- def get_klass_name_from_node(node)
173
- return Lutaml::Model::Serializable.to_s unless node
174
-
175
- node.attribute_nodes.find { |attr| attr.name == "name" }.value
176
- end
177
-
178
- def gen_map_attribute_line(attr_name, attr_method)
179
- space_before = " " * 10
180
- method_name = Lutaml::Model::Utils.snake_case(attr_method)
181
-
182
- map_attributes = MAP_ATTRIBUTES
183
- .gsub("#ATTRIBUTE_NAME#", attr_name)
184
- .gsub("#ATTRIBUTE_METHOD#", method_name)
185
-
186
- "#{space_before}#{map_attributes}"
187
- end
188
-
189
- def gen_attribute_line(tag_name, attribute_type = ":string")
190
- tag_name = Lutaml::Model::Utils.snake_case(tag_name)
191
- space_before = " " * 8
192
-
193
- attribute_line = ATTRIBUTE_LINE
194
- .gsub("#TAG_NAME#", tag_name)
195
- .gsub("#ATTRIBUTE_TYPE#", attribute_type)
196
-
197
- "#{space_before}#{attribute_line}"
198
- end
199
-
200
- def get_tag_name(tag)
201
- tag_name = tag.attribute_nodes.find { |attr| attr.name == "name" }.value
202
- tag_name == "xmlns" ? "altered_xmlns" : tag_name
203
- end
204
-
205
- def gen_tags(node)
206
- tags = node.search("Tag")
207
- attributes_lines = ""
208
-
209
- tags.each do |tag|
210
- tag_name = get_tag_name(tag)
211
- attributes_lines += gen_attribute_line(tag_name)
212
- end
213
-
214
- [attributes_lines, tags]
215
- end
216
-
217
- def gen_abstract_klass
218
- unless @abstract_klass_node
219
- @abstract_tags = []
220
- return ""
221
- end
222
-
223
- attributes_lines = ""
224
- tags_lines, @abstract_tags = gen_tags(@abstract_klass_node)
225
- attributes_lines += tags_lines
226
- klass_name = get_klass_name_from_node(@abstract_klass_node)
227
-
228
- KLASS_TEMPLATE
229
- .gsub("#KLASS_NAME#", Lutaml::Model::Utils.classify(klass_name))
230
- .gsub("#FROM_KLASS#", "Lutaml::Model::Serializable")
231
- .gsub("#ATTRIBUTES#", attributes_lines.rstrip)
232
- .gsub("#XML_MAPPING#", "")
233
- .gsub("#ROOT_TAG_LINE#", "")
234
- end
235
-
236
- def gen_apply_types(node)
237
- apply_types_lines = ""
238
- apply_types_nodes = node.search("Apply")
239
- apply_types_nodes.each do |n|
240
- apply_types = n.attribute_nodes.map(&:value)
241
- apply_types.each do |apply_type|
242
- tag_name = "base_#{apply_type}"
243
- apply_types_lines += gen_attribute_line(tag_name)
244
- end
245
- end
246
-
247
- [apply_types_lines, apply_types_nodes]
248
- end
249
-
250
- def gen_generic_klass(node, from_klass = nil)
251
- node_name = get_klass_name_from_node(node)
252
- attributes_lines, map_attributes_lines = gen_klass_tags(node)
253
- apply_types_lines, apply_types_nodes = gen_apply_types(node)
254
- attributes_lines, map_attributes_lines = gen_klass_apply_types(
255
- attributes_lines, map_attributes_lines,
256
- apply_types_lines, apply_types_nodes
257
- )
258
-
259
- map_attributes_lines = node_map_attributes(node_name,
260
- from_klass, map_attributes_lines)
261
- xml_mapping = replace_xml_mapping(node_name, map_attributes_lines)
262
-
263
- replace_klass_template(node_name, attributes_lines,
264
- xml_mapping, from_klass)
265
- end
266
-
267
- def node_map_attributes(node_name, from_klass, map_attributes_lines)
268
- if from_klass && @node_map.key?(from_klass.to_sym)
269
- map_attributes_lines += @node_map[from_klass.to_sym].to_s
270
- else
271
- @node_map ||= {}
272
- @node_map[node_name.to_sym] = map_attributes_lines
273
- end
274
-
275
- map_attributes_lines
276
- end
277
-
278
- def gen_klass_apply_types(attributes_lines, map_attributes_lines,
279
- apply_types_lines, apply_types_nodes)
280
- unless apply_types_nodes.empty?
281
- attributes_lines += apply_types_lines
282
- apply_types_nodes.each do |n|
283
- apply_types = n.attribute_nodes.map(&:value)
284
- apply_types.each do |apply_type|
285
- map_attributes_lines += gen_map_attribute_line(
286
- "base_#{apply_type}", "base_#{apply_type}"
287
- )
288
- end
289
- end
290
- end
291
-
292
- [attributes_lines, map_attributes_lines]
293
- end
294
-
295
- def gen_klass_tags(node)
296
- attributes_lines = ""
297
- map_attributes_lines = ""
298
-
299
- tags_lines, tags = gen_tags(node)
300
- attributes_lines += tags_lines
301
- (@abstract_tags + tags).each do |tag|
302
- tag_name = get_tag_name(tag)
303
- map_attributes_lines += gen_map_attribute_line(tag_name, tag_name)
304
- end
305
-
306
- [attributes_lines, map_attributes_lines]
307
- end
308
-
309
- def replace_xml_mapping(node_name, map_attributes_lines)
310
- # Look up namespace class by URI, or generate a new one if not found
311
- ns_class = find_or_create_namespace_class
312
-
313
- XML_MAPPING
314
- .gsub("#ROOT_TAG#", node_name)
315
- .gsub("#NAMESPACE_CLASS#", ns_class)
316
- .gsub("#MAP_ATTRIBUTES#", "\n#{map_attributes_lines.rstrip}")
317
- .rstrip
318
- end
319
-
320
- def find_or_create_namespace_class
321
- uri = @def_namespace[:uri]
322
- prefix = @def_namespace[:name]
323
-
324
- # Try to find existing namespace class in registry
325
- existing_class = NamespaceRegistry.resolve(uri)
326
- return existing_class.name if existing_class
327
-
328
- # Generate a new namespace class
329
- # Format: Xmi::Namespace::Dynamic::{ModuleName}
330
- module_name = @module_name
331
- ns_class_name = "Xmi::Namespace::Dynamic::#{module_name}"
332
-
333
- # Check if already defined
334
- return ns_class_name if Object.const_defined?(ns_class_name)
335
-
336
- # Create the namespace class
337
- Namespace.ensure_dynamic_namespace_module_exists!
338
- ns_class = Class.new(Lutaml::Xml::Namespace) do
339
- define_singleton_method(:uri) { uri }
340
- define_singleton_method(:prefix_default) { prefix }
341
- end
342
- Namespace::Dynamic.const_set(module_name, ns_class)
343
-
344
- # Register in namespace registry
345
- NamespaceRegistry.register(uri, ns_class)
346
-
347
- ns_class_name
348
- end
349
-
350
- def replace_klass_template(node_name, attributes_lines, xml_mapping,
351
- from_klass = nil)
352
- abstract_klass_name = get_klass_name_from_node(@abstract_klass_node)
353
- abstract_klass_name = from_klass if from_klass
354
- root_tag_line = "def self.root_tag = \"#{node_name}\""
355
-
356
- KLASS_TEMPLATE
357
- .gsub("#KLASS_NAME#", Lutaml::Model::Utils.classify(node_name))
358
- .gsub("#FROM_KLASS#", Lutaml::Model::Utils.classify(abstract_klass_name))
359
- .gsub("#ROOT_TAG_LINE#", root_tag_line)
360
- .gsub("#ATTRIBUTES#", attributes_lines.rstrip)
361
- .gsub("#XML_MAPPING#", "\n\n#{xml_mapping}")
362
- end
363
-
364
- def gen_generic_klasses(xmi_doc)
365
- nodes = xmi_doc.xpath("//UMLProfiles//Stereotypes//Stereotype[not(contains(@isAbstract, 'true'))]")
366
- klasses_lines = ""
367
-
368
- klasses_lines += "#{gen_generic_klasses_wo_base_stereotypes(nodes)}\n"
369
- klasses_lines += "#{gen_generic_klasses_w_base_stereotypes(nodes)}\n"
370
-
371
- klasses_lines
372
- end
373
-
374
- def gen_generic_klasses_wo_base_stereotypes(nodes)
375
- nodes = nodes.select { |n| n.attributes["baseStereotypes"].nil? }
376
- klasses_lines = ""
377
-
378
- nodes.each do |node|
379
- klasses_lines += "#{gen_generic_klass(node)}\n"
380
- end
381
-
382
- klasses_lines
383
- end
384
-
385
- def gen_generic_klasses_w_base_stereotypes(nodes)
386
- nodes = nodes.reject { |n| n.attributes["baseStereotypes"].nil? }
387
- klasses_lines = ""
388
-
389
- nodes.each do |node|
390
- base_stereotypes = node.attributes["baseStereotypes"].value
391
- klasses_lines += "#{gen_generic_klass(node, base_stereotypes)}\n"
392
- end
393
-
394
- klasses_lines
395
- end
396
-
397
- def gen_klasses(xmi_doc)
398
- @abstract_klass_node = get_abstract_klass_node(xmi_doc)
399
- klasses_lines = ""
400
- klasses_lines += "#{gen_abstract_klass}\n"
401
- klasses_lines += gen_generic_klasses(xmi_doc).rstrip
402
- klasses_lines
403
- end
404
-
405
- def gen_module(xmi_doc, module_name)
406
- MODULE_TEMPLATE
407
- .gsub("#MODULE_NAME#", module_name)
408
- .gsub("#KLASSES#", gen_klasses(xmi_doc))
409
- end
410
-
411
- def get_namespace_from_definition(xmi_doc)
412
- namespace_key = get_module_name_from_definition(xmi_doc)
413
- namespace_uri = get_module_uri(xmi_doc)
414
-
415
- { name: namespace_key, uri: namespace_uri }
416
- end
417
-
418
- def gen_content(xml)
419
- xmi_doc = Nokogiri::XML(File.read(xml))
420
- @module_name = get_module_name(xmi_doc)
421
- @def_namespace = get_namespace_from_definition(xmi_doc)
422
- gen_module(xmi_doc, @module_name)
423
- end
424
-
425
- def get_module_name(xmi_doc)
426
- get_module_name_from_definition(xmi_doc).capitalize
427
- end
428
-
429
- def get_module_name_from_definition(xmi_doc)
430
- node = xmi_doc.at_xpath("//UMLProfile/Documentation")
431
- node.attribute_nodes.find { |attr| attr.name == "name" }&.value
432
- end
433
-
434
- def get_module_uri(xmi_doc)
435
- node = xmi_doc.at_xpath("//UMLProfile/Documentation")
436
- uri = node.attribute_nodes.find { |attr| attr.name == "URI" }&.value
437
-
438
- return uri if uri
439
-
440
- name = get_module_name_from_definition(xmi_doc)
441
- ver = node.attribute_nodes.find { |attr| attr.name == "version" }&.value
442
-
443
- "http://www.sparxsystems.com/profiles/#{name}/#{ver}"
54
+ def derive_module_name(xml_path)
55
+ xmi_doc = Nokogiri::XML(File.read(xml_path))
56
+ get_module_name(xmi_doc)
444
57
  end
445
58
  end
446
59
  end
data/lib/xmi/extension.rb CHANGED
@@ -2,12 +2,8 @@
2
2
 
3
3
  module Xmi
4
4
  class Extension < Lutaml::Model::Serializable
5
- attribute :id, ::Xmi::Type::XmiId
6
- attribute :label, ::Xmi::Type::XmiLabel
7
- attribute :uuid, ::Xmi::Type::XmiUuid
8
- attribute :href, :string
9
- attribute :idref, ::Xmi::Type::XmiIdRef
10
- attribute :type, ::Xmi::Type::XmiType
5
+ include XmiIdentity::Attributes
6
+
11
7
  attribute :extender, :string
12
8
  attribute :extender_id, :string
13
9
 
@@ -15,12 +11,8 @@ module Xmi
15
11
  root "Extension"
16
12
  namespace ::Xmi::Namespace::Omg::Xmi
17
13
 
18
- map_attribute "id", to: :id
19
- map_attribute "label", to: :label
20
- map_attribute "uuid", to: :uuid
21
- map_attribute "href", to: :href
22
- map_attribute "idref", to: :idref
23
- map_attribute "type", to: :type
14
+ XmiIdentity.apply_xml_mappings(self)
15
+
24
16
  map_attribute "extender", to: :extender
25
17
  map_attribute "extenderID", to: :extender_id
26
18
  end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ # Composable pipeline for XMI parsing.
5
+ #
6
+ # Each step receives a context hash and returns an updated context hash.
7
+ # New concerns (validation, logging, caching) are added by inserting steps
8
+ # without modifying existing code — Open/Closed Principle.
9
+ #
10
+ # @example
11
+ # context = { xml: xml_content, root_class: Xmi::Sparx::SparxRoot }
12
+ # result = Xmi::ParserPipeline.run(context)
13
+ # result[:root] # => parsed SparxRoot instance
14
+ #
15
+ module ParserPipeline
16
+ module Steps
17
+ module FixEncoding
18
+ def self.call(ctx)
19
+ xml = ctx[:xml]
20
+ if xml.respond_to?(:valid_encoding?) && !xml.valid_encoding?
21
+ xml = xml
22
+ .encode("UTF-16be", invalid: :replace, replace: "?")
23
+ .encode("UTF-8")
24
+ end
25
+ ctx.merge(xml: xml)
26
+ end
27
+ end
28
+
29
+ module InitVersioning
30
+ def self.call(ctx)
31
+ Xmi.init_versioning!
32
+ ctx
33
+ end
34
+ end
35
+
36
+ module ParseXml
37
+ def self.call(ctx)
38
+ root_class = ctx[:root_class]
39
+ root = VersionRegistry.parse_with_detected_version(ctx[:xml],
40
+ root_class)
41
+ ctx.merge(root: root)
42
+ end
43
+ end
44
+
45
+ module BuildIndex
46
+ def self.call(ctx)
47
+ root = ctx[:root]
48
+ root.build_index if root.respond_to?(:build_index)
49
+ ctx
50
+ end
51
+ end
52
+ end
53
+
54
+ DEFAULT_STEPS = [
55
+ Steps::FixEncoding,
56
+ Steps::InitVersioning,
57
+ Steps::ParseXml,
58
+ Steps::BuildIndex,
59
+ ].freeze
60
+
61
+ # Run the pipeline with default or custom steps.
62
+ #
63
+ # @param ctx [Hash] Initial context with :xml and :root_class
64
+ # @param steps [Array<Module>] Pipeline steps (defaults to DEFAULT_STEPS)
65
+ # @return [Hash] Final context including :root
66
+ def self.run(ctx, steps: DEFAULT_STEPS)
67
+ steps.reduce(ctx) { |context, step| step.call(context) }
68
+ end
69
+ end
70
+ end
data/lib/xmi/root.rb CHANGED
@@ -5,12 +5,8 @@ require_relative "uml"
5
5
 
6
6
  module Xmi
7
7
  class Root < Lutaml::Model::Serializable
8
- attribute :id, ::Xmi::Type::XmiId
9
- attribute :label, ::Xmi::Type::XmiLabel
10
- attribute :uuid, ::Xmi::Type::XmiUuid
11
- attribute :href, :string
12
- attribute :idref, ::Xmi::Type::XmiIdRef
13
- attribute :type, ::Xmi::Type::XmiType
8
+ include XmiIdentity::Attributes
9
+
14
10
  attribute :documentation, Documentation
15
11
  attribute :bibliography, CustomProfile::Bibliography, collection: true
16
12
  attribute :basic_doc, CustomProfile::BasicDoc, collection: true
@@ -44,12 +40,7 @@ module Xmi
44
40
  ::Xmi::Namespace::Sparx::CityGml,
45
41
  ]
46
42
 
47
- map_attribute "id", to: :id
48
- map_attribute "label", to: :label
49
- map_attribute "uuid", to: :uuid
50
- map_attribute "href", to: :href
51
- map_attribute "idref", to: :idref
52
- map_attribute "type", to: :type
43
+ XmiIdentity.apply_xml_mappings(self)
53
44
 
54
45
  map_element "Documentation", to: :documentation
55
46
  map_element "Model", to: :model
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ module Sparx
5
+ module Connector
6
+ class Appearance < Lutaml::Model::Serializable
7
+ attribute :linemode, :integer
8
+ attribute :linecolor, :integer
9
+ attribute :linewidth, :integer
10
+ attribute :seqno, :integer
11
+ attribute :headStyle, :integer
12
+ attribute :lineStyle, :integer
13
+
14
+ xml do
15
+ root "appearance"
16
+
17
+ map_attribute :linemode, to: :linemode
18
+ map_attribute :linecolor, to: :linecolor
19
+ map_attribute :linewidth, to: :linewidth
20
+ map_attribute :seqno, to: :seqno
21
+ map_attribute :headStyle, to: :headStyle
22
+ map_attribute :lineStyle, to: :lineStyle
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ module Sparx
5
+ module Connector
6
+ class Connector < Lutaml::Model::Serializable
7
+ attribute :name, :string
8
+ attribute :idref, ::Xmi::Type::XmiIdRef
9
+ attribute :source, Source
10
+ attribute :target, Target
11
+ attribute :model, Model
12
+ attribute :properties, Properties
13
+ attribute :documentation, Element::Documentation
14
+ attribute :appearance, Appearance
15
+ attribute :labels, Labels
16
+ attribute :extended_properties, Element::ExtendedProperties
17
+ attribute :style, Element::Style
18
+ attribute :tags, Element::Tags
19
+ attribute :xrefs, Element::Xrefs
20
+
21
+ xml do
22
+ root "element"
23
+
24
+ map_attribute "name", to: :name
25
+ map_attribute "idref", to: :idref
26
+
27
+ map_element "source", to: :source
28
+ map_element "target", to: :target
29
+ map_element "model", to: :model
30
+ map_element "properties", to: :properties
31
+ map_element "documentation", to: :documentation, value_map: VALUE_MAP
32
+ map_element "appearance", to: :appearance
33
+ map_element "labels", to: :labels, render_nil: true
34
+ map_element "extendedProperties", to: :extended_properties
35
+ map_element "style", to: :style, render_nil: true
36
+ map_element "xrefs", to: :xrefs, render_nil: true
37
+ map_element "tags", to: :tags, render_nil: true
38
+ end
39
+ end
40
+
41
+ class Connectors < Lutaml::Model::Serializable
42
+ attribute :connector, Connector, collection: true
43
+
44
+ xml do
45
+ root "connectors"
46
+
47
+ map_element "connector", to: :connector, value_map: VALUE_MAP
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end