xmi 0.5.4 → 0.5.6

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +29 -47
  3. data/CLAUDE.md +166 -0
  4. data/README.adoc +9 -27
  5. data/TODO.perf/01-eliminate-duplicate-nokogiri-parse.md +33 -0
  6. data/TODO.perf/02-read-only-fast-mode.md +38 -0
  7. data/TODO.perf/03-register-fallback-idempotency.md +20 -0
  8. data/TODO.perf/04-pipeline-hash-mutation.md +31 -0
  9. data/TODO.perf/05-ea-root-single-parse.md +29 -0
  10. data/docs/migration.md +6 -6
  11. data/docs/versioning.md +1 -1
  12. data/lib/tasks/benchmark_runner.rb +1 -1
  13. data/lib/xmi/custom_profile/abstract.rb +16 -0
  14. data/lib/xmi/custom_profile/basic_doc.rb +16 -0
  15. data/lib/xmi/custom_profile/bibliography.rb +16 -0
  16. data/lib/xmi/custom_profile/edition.rb +18 -0
  17. data/lib/xmi/custom_profile/enumeration.rb +16 -0
  18. data/lib/xmi/custom_profile/informative.rb +16 -0
  19. data/lib/xmi/custom_profile/invariant.rb +16 -0
  20. data/lib/xmi/custom_profile/number.rb +18 -0
  21. data/lib/xmi/custom_profile/ocl.rb +16 -0
  22. data/lib/xmi/custom_profile/persistence.rb +20 -0
  23. data/lib/xmi/custom_profile/publication_date.rb +18 -0
  24. data/lib/xmi/custom_profile/year_version.rb +18 -0
  25. data/lib/xmi/custom_profile.rb +12 -143
  26. data/lib/xmi/ea_root/code_generation.rb +140 -0
  27. data/lib/xmi/ea_root/extension_lifecycle.rb +52 -0
  28. data/lib/xmi/ea_root/namespace_handling.rb +39 -0
  29. data/lib/xmi/ea_root/xml_parsing.rb +51 -0
  30. data/lib/xmi/ea_root.rb +14 -407
  31. data/lib/xmi/namespace_detector.rb +35 -3
  32. data/lib/xmi/parser_pipeline.rb +9 -9
  33. data/lib/xmi/sparx/index.rb +2 -2
  34. data/lib/xmi/sparx/mappings/base_mapping.rb +4 -4
  35. data/lib/xmi/sparx/mappings.rb +1 -1
  36. data/lib/xmi/sparx/root.rb +3 -3
  37. data/lib/xmi/sparx.rb +2 -2
  38. data/lib/xmi/version.rb +1 -1
  39. data/lib/xmi/version_registry.rb +11 -8
  40. metadata +24 -2
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ module CustomProfile
5
+ class Bibliography < Lutaml::Model::Serializable
6
+ attribute :base_class, :string
7
+
8
+ xml do
9
+ element "Bibliography"
10
+ namespace ::Xmi::Namespace::Sparx::CustomProfile
11
+
12
+ map_attribute "base_Class", to: :base_class
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ module CustomProfile
5
+ class Edition < Lutaml::Model::Serializable
6
+ attribute :base_package, :string
7
+ attribute :edition, :string
8
+
9
+ xml do
10
+ element "edition"
11
+ namespace ::Xmi::Namespace::Sparx::CustomProfile
12
+
13
+ map_attribute "base_Package", to: :base_package
14
+ map_attribute "edition", to: :edition
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ module CustomProfile
5
+ class Enumeration < Lutaml::Model::Serializable
6
+ attribute :base_enumeration, :string
7
+
8
+ xml do
9
+ element "enumeration"
10
+ namespace ::Xmi::Namespace::Sparx::CustomProfile
11
+
12
+ map_attribute "base_Enumeration", to: :base_enumeration
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ module CustomProfile
5
+ class Informative < Lutaml::Model::Serializable
6
+ attribute :base_package, :string
7
+
8
+ xml do
9
+ element "informative"
10
+ namespace ::Xmi::Namespace::Sparx::CustomProfile
11
+
12
+ map_attribute "base_Package", to: :base_package
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ module CustomProfile
5
+ class Invariant < Lutaml::Model::Serializable
6
+ attribute :base_constraint, :string
7
+
8
+ xml do
9
+ element "invariant"
10
+ namespace ::Xmi::Namespace::Sparx::CustomProfile
11
+
12
+ map_attribute "base_Constraint", to: :base_constraint
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ module CustomProfile
5
+ class Number < Lutaml::Model::Serializable
6
+ attribute :base_package, :string
7
+ attribute :number, :string
8
+
9
+ xml do
10
+ element "number"
11
+ namespace ::Xmi::Namespace::Sparx::CustomProfile
12
+
13
+ map_attribute "base_Package", to: :base_package
14
+ map_attribute "number", to: :number
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ module CustomProfile
5
+ class Ocl < Lutaml::Model::Serializable
6
+ attribute :base_constraint, :string
7
+
8
+ xml do
9
+ element "OCL"
10
+ namespace ::Xmi::Namespace::Sparx::CustomProfile
11
+
12
+ map_attribute "base_Constraint", to: :base_constraint
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ module CustomProfile
5
+ class Persistence < Lutaml::Model::Serializable
6
+ attribute :base_class, :string
7
+ attribute :base_enumeration, :string
8
+ attribute :persistence, :string
9
+
10
+ xml do
11
+ element "persistence"
12
+ namespace ::Xmi::Namespace::Sparx::CustomProfile
13
+
14
+ map_attribute "base_Class", to: :base_class
15
+ map_attribute "base_Enumeration", to: :base_enumeration
16
+ map_attribute "persistence", to: :persistence
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ module CustomProfile
5
+ class PublicationDate < Lutaml::Model::Serializable
6
+ attribute :base_package, :string
7
+ attribute :publication_date, :string
8
+
9
+ xml do
10
+ element "publicationDate"
11
+ namespace ::Xmi::Namespace::Sparx::CustomProfile
12
+
13
+ map_attribute "base_Package", to: :base_package
14
+ map_attribute "publicationDate", to: :publication_date
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ module CustomProfile
5
+ class YearVersion < Lutaml::Model::Serializable
6
+ attribute :base_package, :string
7
+ attribute :year_version, :string
8
+
9
+ xml do
10
+ element "yearVersion"
11
+ namespace ::Xmi::Namespace::Sparx::CustomProfile
12
+
13
+ map_attribute "base_Package", to: :base_package
14
+ map_attribute "yearVersion", to: :year_version
15
+ end
16
+ end
17
+ end
18
+ end
@@ -2,148 +2,17 @@
2
2
 
3
3
  module Xmi
4
4
  module CustomProfile
5
- class Bibliography < Lutaml::Model::Serializable
6
- attribute :base_class, :string
7
-
8
- xml do
9
- element "Bibliography"
10
- namespace ::Xmi::Namespace::Sparx::CustomProfile
11
-
12
- map_attribute "base_Class", to: :base_class
13
- end
14
- end
15
-
16
- class BasicDoc < Lutaml::Model::Serializable
17
- attribute :base_class, :string
18
-
19
- xml do
20
- element "BasicDoc"
21
- namespace ::Xmi::Namespace::Sparx::CustomProfile
22
-
23
- map_attribute "base_Class", to: :base_class
24
- end
25
- end
26
-
27
- class Enumeration < Lutaml::Model::Serializable
28
- attribute :base_enumeration, :string
29
-
30
- xml do
31
- element "enumeration"
32
- namespace ::Xmi::Namespace::Sparx::CustomProfile
33
-
34
- map_attribute "base_Enumeration", to: :base_enumeration
35
- end
36
- end
37
-
38
- class Ocl < Lutaml::Model::Serializable
39
- attribute :base_constraint, :string
40
-
41
- xml do
42
- element "OCL"
43
- namespace ::Xmi::Namespace::Sparx::CustomProfile
44
-
45
- map_attribute "base_Constraint", to: :base_constraint
46
- end
47
- end
48
-
49
- class Invariant < Lutaml::Model::Serializable
50
- attribute :base_constraint, :string
51
-
52
- xml do
53
- element "invariant"
54
- namespace ::Xmi::Namespace::Sparx::CustomProfile
55
-
56
- map_attribute "base_Constraint", to: :base_constraint
57
- end
58
- end
59
-
60
- class PublicationDate < Lutaml::Model::Serializable
61
- attribute :base_package, :string
62
- attribute :publication_date, :string
63
-
64
- xml do
65
- element "publicationDate"
66
- namespace ::Xmi::Namespace::Sparx::CustomProfile
67
-
68
- map_attribute "base_Package", to: :base_package
69
- map_attribute "publicationDate", to: :publication_date
70
- end
71
- end
72
-
73
- class Edition < Lutaml::Model::Serializable
74
- attribute :base_package, :string
75
- attribute :edition, :string
76
-
77
- xml do
78
- element "edition"
79
- namespace ::Xmi::Namespace::Sparx::CustomProfile
80
-
81
- map_attribute "base_Package", to: :base_package
82
- map_attribute "edition", to: :edition
83
- end
84
- end
85
-
86
- class Number < Lutaml::Model::Serializable
87
- attribute :base_package, :string
88
- attribute :number, :string
89
-
90
- xml do
91
- element "number"
92
- namespace ::Xmi::Namespace::Sparx::CustomProfile
93
-
94
- map_attribute "base_Package", to: :base_package
95
- map_attribute "number", to: :number
96
- end
97
- end
98
-
99
- class YearVersion < Lutaml::Model::Serializable
100
- attribute :base_package, :string
101
- attribute :year_version, :string
102
-
103
- xml do
104
- element "yearVersion"
105
- namespace ::Xmi::Namespace::Sparx::CustomProfile
106
-
107
- map_attribute "base_Package", to: :base_package
108
- map_attribute "yearVersion", to: :year_version
109
- end
110
- end
111
-
112
- class Informative < Lutaml::Model::Serializable
113
- attribute :base_package, :string
114
-
115
- xml do
116
- element "informative"
117
- namespace ::Xmi::Namespace::Sparx::CustomProfile
118
-
119
- map_attribute "base_Package", to: :base_package
120
- end
121
- end
122
-
123
- class Persistence < Lutaml::Model::Serializable
124
- attribute :base_class, :string
125
- attribute :base_enumeration, :string
126
- attribute :persistence, :string
127
-
128
- xml do
129
- element "persistence"
130
- namespace ::Xmi::Namespace::Sparx::CustomProfile
131
-
132
- map_attribute "base_Class", to: :base_class
133
- map_attribute "base_Enumeration", to: :base_enumeration
134
- map_attribute "persistence", to: :persistence
135
- end
136
- end
137
-
138
- class Abstract < Lutaml::Model::Serializable
139
- attribute :base_class, :string
140
-
141
- xml do
142
- element "Abstract"
143
- namespace ::Xmi::Namespace::Sparx::CustomProfile
144
-
145
- map_attribute "base_Class", to: :base_class
146
- end
147
- end
5
+ autoload :Bibliography, "xmi/custom_profile/bibliography"
6
+ autoload :BasicDoc, "xmi/custom_profile/basic_doc"
7
+ autoload :Enumeration, "xmi/custom_profile/enumeration"
8
+ autoload :Ocl, "xmi/custom_profile/ocl"
9
+ autoload :Invariant, "xmi/custom_profile/invariant"
10
+ autoload :PublicationDate, "xmi/custom_profile/publication_date"
11
+ autoload :Edition, "xmi/custom_profile/edition"
12
+ autoload :Number, "xmi/custom_profile/number"
13
+ autoload :YearVersion, "xmi/custom_profile/year_version"
14
+ autoload :Informative, "xmi/custom_profile/informative"
15
+ autoload :Persistence, "xmi/custom_profile/persistence"
16
+ autoload :Abstract, "xmi/custom_profile/abstract"
148
17
  end
149
18
  end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ class EaRoot
5
+ module CodeGeneration
6
+ # Build extension classes directly via Class.new + const_set.
7
+ def build_extension(xmi_doc)
8
+ @module_name = get_module_name(xmi_doc)
9
+ @def_namespace = get_namespace_from_definition(xmi_doc)
10
+
11
+ # Resolve namespace class (returns class object)
12
+ ns_class = resolve_namespace_class
13
+
14
+ # Create the parent module Xmi::EaRoot::ModuleName
15
+ mod = Module.new
16
+ EaRoot.const_set(@module_name.to_sym, mod)
17
+
18
+ # Process abstract class
19
+ @abstract_klass_node = get_abstract_klass_node(xmi_doc)
20
+ @abstract_tags = []
21
+ abstract_klass = nil
22
+ if @abstract_klass_node
23
+ abstract_klass = build_abstract_class(mod, @abstract_klass_node)
24
+ end
25
+
26
+ # Process non-abstract stereotypes
27
+ nodes = xmi_doc.xpath(
28
+ "//UMLProfiles//Stereotypes//Stereotype[not(contains(@isAbstract, 'true'))]",
29
+ )
30
+
31
+ # Without baseStereotypes
32
+ nodes.select { |n| n.attributes["baseStereotypes"].nil? }.each do |node|
33
+ build_stereotype_class(mod, node, abstract_klass, ns_class)
34
+ end
35
+
36
+ # With baseStereotypes
37
+ nodes.reject { |n| n.attributes["baseStereotypes"].nil? }.each do |node|
38
+ base_name = node.attributes["baseStereotypes"].value
39
+ build_stereotype_class(mod, node, abstract_klass, ns_class,
40
+ parent_name: base_name)
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def build_abstract_class(parent_mod, abstract_node)
47
+ klass_name = Lutaml::Model::Utils.classify(
48
+ get_klass_name_from_node(abstract_node),
49
+ )
50
+
51
+ klass = Class.new(Lutaml::Model::Serializable)
52
+
53
+ # Add tag attributes (NO xml mapping for abstract classes)
54
+ tags = abstract_node.search("Tag")
55
+ @abstract_tags = tags
56
+ tags.each do |tag|
57
+ tag_name = get_tag_name(tag)
58
+ attr_name = Lutaml::Model::Utils.snake_case(tag_name)
59
+ klass.attribute attr_name.to_sym, :string
60
+ end
61
+
62
+ parent_mod.const_set(klass_name.to_sym, klass)
63
+ klass
64
+ end
65
+
66
+ def build_stereotype_class(parent_mod, node, abstract_klass, ns_class,
67
+ parent_name: nil)
68
+ node_name = get_klass_name_from_node(node)
69
+ klass_name = Lutaml::Model::Utils.classify(node_name)
70
+
71
+ # Determine parent class
72
+ parent_klass = if parent_name
73
+ parent_mod.const_get(Lutaml::Model::Utils.classify(parent_name).to_sym)
74
+ elsif abstract_klass
75
+ abstract_klass
76
+ else
77
+ Lutaml::Model::Serializable
78
+ end
79
+
80
+ # Collect tags and apply types
81
+ tags = node.search("Tag")
82
+ apply_nodes = node.search("Apply")
83
+
84
+ # Build the class
85
+ klass = Class.new(parent_klass) do
86
+ define_singleton_method(:root_tag) { node_name }
87
+ end
88
+
89
+ # Add tag attributes
90
+ tags.each do |tag|
91
+ tag_name = get_tag_name(tag)
92
+ attr_name = Lutaml::Model::Utils.snake_case(tag_name)
93
+ klass.attribute attr_name.to_sym, :string
94
+ end
95
+
96
+ # Add apply type attributes
97
+ apply_nodes.each do |n|
98
+ apply_types = n.attribute_nodes.map(&:value)
99
+ apply_types.each do |apply_type|
100
+ attr_name = Lutaml::Model::Utils.snake_case("base_#{apply_type}")
101
+ klass.attribute attr_name.to_sym, :string
102
+ end
103
+ end
104
+
105
+ # Add XML mapping
106
+ # Pre-compute tag mapping pairs (xml block runs in Mapping context, not EaRoot)
107
+ all_tag_pairs = (@abstract_tags + tags).map do |tag|
108
+ tag_name = get_tag_name(tag)
109
+ [tag_name, Lutaml::Model::Utils.snake_case(tag_name).to_sym]
110
+ end
111
+
112
+ apply_pairs = []
113
+ apply_nodes.each do |n|
114
+ n.attribute_nodes.map(&:value).each do |apply_type|
115
+ apply_pairs << [
116
+ "base_#{apply_type}",
117
+ Lutaml::Model::Utils.snake_case("base_#{apply_type}").to_sym,
118
+ ]
119
+ end
120
+ end
121
+
122
+ klass.xml do
123
+ root node_name
124
+ namespace ns_class
125
+
126
+ all_tag_pairs.each do |xml_name, attr_sym|
127
+ map_attribute xml_name, to: attr_sym
128
+ end
129
+
130
+ apply_pairs.each do |xml_name, attr_sym|
131
+ map_attribute xml_name, to: attr_sym
132
+ end
133
+ end
134
+
135
+ parent_mod.const_set(klass_name.to_sym, klass)
136
+ klass
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ class EaRoot
5
+ module ExtensionLifecycle
6
+ def update_mappings(module_name)
7
+ new_klasses = all_new_klasses(module_name)
8
+ inject_model_attributes(new_klasses, module_name)
9
+ inject_model_xml_mappings(new_klasses, module_name)
10
+ end
11
+
12
+ private
13
+
14
+ def inject_model_attributes(new_klasses, module_name)
15
+ sparx_root = Xmi::Sparx::Root
16
+ new_klasses.each do |klass_name|
17
+ method_name = Lutaml::Model::Utils.snake_case(klass_name)
18
+ full_klass = EaRoot.const_get(module_name).const_get(klass_name)
19
+ sparx_root.attribute method_name.to_sym, full_klass, collection: true
20
+ end
21
+ end
22
+
23
+ def inject_model_xml_mappings(new_klasses, module_name)
24
+ map_entries = []
25
+ new_klasses.each do |klass_name|
26
+ klass = EaRoot.const_get(module_name).const_get(klass_name)
27
+ next unless klass.respond_to?(:root_tag)
28
+
29
+ method_name = Lutaml::Model::Utils.snake_case(klass_name)
30
+ map_entries << [klass.root_tag, method_name.to_sym]
31
+ end
32
+
33
+ return if map_entries.empty?
34
+
35
+ Xmi::Sparx::Root.class_eval do
36
+ xml do
37
+ map_entries.each do |element_name, method_sym|
38
+ map_element element_name, to: method_sym,
39
+ value_map: Xmi::VALUE_MAP
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ def all_new_klasses(module_name)
46
+ EaRoot.const_get(module_name).constants.select do |c|
47
+ EaRoot.const_get(module_name).const_get(c).is_a? Class
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ class EaRoot
5
+ module NamespaceHandling
6
+ # Resolve the namespace class for the current extension.
7
+ # Returns the class object (not a name string) for direct use with
8
+ # the xml DSL's +namespace+ directive.
9
+ def resolve_namespace_class
10
+ uri = @def_namespace[:uri]
11
+ prefix = @def_namespace[:name]
12
+
13
+ existing = NamespaceRegistry.resolve(uri)
14
+ return existing if existing
15
+
16
+ module_name = @module_name
17
+ ns_class_name = "Xmi::Namespace::Dynamic::#{module_name}"
18
+ return Object.const_get(ns_class_name) if Object.const_defined?(ns_class_name)
19
+
20
+ Namespace.ensure_dynamic_namespace_module_exists!
21
+ ns_class = Class.new(Lutaml::Xml::Namespace) do
22
+ define_singleton_method(:uri) { uri }
23
+ define_singleton_method(:prefix_default) { prefix }
24
+ end
25
+ Namespace::Dynamic.const_set(module_name, ns_class)
26
+
27
+ NamespaceRegistry.register(uri, ns_class)
28
+
29
+ ns_class
30
+ end
31
+
32
+ # Returns the namespace class _name_ as a string.
33
+ def find_or_create_namespace_class
34
+ klass = resolve_namespace_class
35
+ klass.is_a?(String) ? klass : klass.name
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ class EaRoot
5
+ module XmlParsing
6
+ def get_abstract_klass_node(xmi_doc)
7
+ xmi_doc.at_xpath(
8
+ "//UMLProfiles//Stereotypes//Stereotype[@isAbstract='true']",
9
+ )
10
+ end
11
+
12
+ def get_klass_name_from_node(node)
13
+ return Lutaml::Model::Serializable.to_s unless node
14
+
15
+ node.attribute_nodes.find { |attr| attr.name == "name" }.value
16
+ end
17
+
18
+ def get_module_name(xmi_doc)
19
+ get_module_name_from_definition(xmi_doc).capitalize
20
+ end
21
+
22
+ def get_module_name_from_definition(xmi_doc)
23
+ node = xmi_doc.at_xpath("//UMLProfile/Documentation")
24
+ node.attribute_nodes.find { |attr| attr.name == "name" }&.value
25
+ end
26
+
27
+ def get_module_uri(xmi_doc)
28
+ node = xmi_doc.at_xpath("//UMLProfile/Documentation")
29
+ uri = node.attribute_nodes.find { |attr| attr.name == "URI" }&.value
30
+ return uri if uri
31
+
32
+ name = get_module_name_from_definition(xmi_doc)
33
+ ver = node.attribute_nodes.find { |attr| attr.name == "version" }&.value
34
+
35
+ "http://www.sparxsystems.com/profiles/#{name}/#{ver}"
36
+ end
37
+
38
+ def get_namespace_from_definition(xmi_doc)
39
+ namespace_key = get_module_name_from_definition(xmi_doc)
40
+ namespace_uri = get_module_uri(xmi_doc)
41
+
42
+ { name: namespace_key, uri: namespace_uri }
43
+ end
44
+
45
+ def get_tag_name(tag)
46
+ tag_name = tag.attribute_nodes.find { |attr| attr.name == "name" }.value
47
+ tag_name == "xmlns" ? "altered_xmlns" : tag_name
48
+ end
49
+ end
50
+ end
51
+ end