xmi 0.3.21 → 0.5.0

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +13 -6
  3. data/.gitignore +2 -1
  4. data/.rubocop.yml +12 -13
  5. data/.rubocop_todo.yml +150 -13
  6. data/CHANGELOG.md +55 -0
  7. data/CODE_OF_CONDUCT.md +84 -0
  8. data/Gemfile +10 -0
  9. data/README.adoc +319 -6
  10. data/benchmark_parse.rb +60 -0
  11. data/docs/migration.md +141 -0
  12. data/docs/versioning.md +255 -0
  13. data/lib/xmi/add.rb +14 -38
  14. data/lib/xmi/{the_custom_profile.rb → custom_profile.rb} +25 -25
  15. data/lib/xmi/delete.rb +14 -38
  16. data/lib/xmi/difference.rb +14 -38
  17. data/lib/xmi/documentation.rb +16 -101
  18. data/lib/xmi/ea_root.rb +114 -33
  19. data/lib/xmi/extension.rb +6 -6
  20. data/lib/xmi/namespace/dynamic.rb +28 -0
  21. data/lib/xmi/namespace/omg.rb +81 -0
  22. data/lib/xmi/namespace/sparx.rb +39 -0
  23. data/lib/xmi/namespace.rb +9 -0
  24. data/lib/xmi/namespace_detector.rb +138 -0
  25. data/lib/xmi/namespace_registry.rb +119 -0
  26. data/lib/xmi/parsing.rb +113 -0
  27. data/lib/xmi/replace.rb +14 -38
  28. data/lib/xmi/root.rb +49 -213
  29. data/lib/xmi/sparx/connector.rb +241 -0
  30. data/lib/xmi/sparx/custom_profile.rb +19 -0
  31. data/lib/xmi/sparx/diagram.rb +97 -0
  32. data/lib/xmi/sparx/ea_stub.rb +20 -0
  33. data/lib/xmi/{extensions/eauml.rb → sparx/ea_uml.rb} +3 -2
  34. data/lib/xmi/sparx/element.rb +453 -0
  35. data/lib/xmi/sparx/extension.rb +43 -0
  36. data/lib/xmi/{extensions → sparx}/gml.rb +9 -3
  37. data/lib/xmi/sparx/mappings/base_mapping.rb +182 -0
  38. data/lib/xmi/sparx/mappings.rb +10 -0
  39. data/lib/xmi/sparx/primitive_type.rb +18 -0
  40. data/lib/xmi/sparx/root.rb +60 -0
  41. data/lib/xmi/sparx/sys_ph_s.rb +18 -0
  42. data/lib/xmi/sparx.rb +17 -1376
  43. data/lib/xmi/type.rb +37 -0
  44. data/lib/xmi/uml.rb +191 -469
  45. data/lib/xmi/v20110701.rb +81 -0
  46. data/lib/xmi/v20131001.rb +68 -0
  47. data/lib/xmi/v20161101.rb +61 -0
  48. data/lib/xmi/version.rb +1 -1
  49. data/lib/xmi/version_registry.rb +164 -0
  50. data/lib/xmi/versioned.rb +142 -0
  51. data/lib/xmi.rb +83 -11
  52. data/scripts-xmi-profile/profile_xmi_simple.rb +213 -0
  53. data/xmi.gemspec +3 -9
  54. metadata +38 -77
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ module Xmi
6
+ # Detects namespace versions from XMI XML content
7
+ #
8
+ # This class parses XMI files to extract namespace URIs and detect
9
+ # the specific versions of XMI, UML, and other OMG specifications used.
10
+ class NamespaceDetector
11
+ VERSION_PATTERN = /(\d{8})/
12
+
13
+ # Namespace URI patterns for OMG specifications
14
+ NS_PATTERNS = {
15
+ xmi: %r{http://www\.omg\.org/spec/XMI/(\d{8})},
16
+ uml: %r{http://www\.omg\.org/spec/UML/(\d{8})},
17
+ umldi: %r{http://www\.omg\.org/spec/UML/(\d{8})/UMLDI},
18
+ umldc: %r{http://www\.omg\.org/spec/UML/(\d{8})/UMLDC},
19
+ }.freeze
20
+
21
+ # Detect all namespace versions from XML content
22
+ #
23
+ # @param xml_content [String] The XML content to parse
24
+ # @return [Hash] A hash with namespace types as keys and version strings as values
25
+ # Example: { xmi: "20131001", uml: "20131001", umldi: nil, umldc: nil }
26
+ def self.detect_versions(xml_content)
27
+ namespaces = extract_namespace_uris(xml_content)
28
+ {
29
+ xmi: detect_version(namespaces, :xmi),
30
+ uml: detect_version(namespaces, :uml),
31
+ umldi: detect_version(namespaces, :umldi),
32
+ umldc: detect_version(namespaces, :umldc),
33
+ }
34
+ end
35
+
36
+ # Extract all namespace URIs from XML content
37
+ #
38
+ # @param xml_content [String] The XML content to parse
39
+ # @return [Hash<String, String>] A hash mapping prefixes to namespace URIs
40
+ def self.extract_namespace_uris(xml_content)
41
+ doc = Nokogiri::XML(xml_content, &:noent)
42
+ doc.collect_namespaces
43
+ rescue Nokogiri::XML::SyntaxError
44
+ {}
45
+ end
46
+
47
+ # Detect version for a specific namespace type
48
+ #
49
+ # @param namespaces [Hash<String, String>] The namespace URIs hash
50
+ # @param type [Symbol] The namespace type (:xmi, :uml, :umldi, :umldc)
51
+ # @return [String, nil] The version string (e.g., "20131001") or nil if not found
52
+ def self.detect_version(namespaces, type)
53
+ pattern = NS_PATTERNS[type]
54
+ return nil unless pattern
55
+
56
+ namespaces.each_value do |uri|
57
+ match = uri.match(pattern)
58
+ return match[1] if match
59
+ end
60
+
61
+ nil
62
+ end
63
+
64
+ # Get the full namespace URI for a detected version
65
+ #
66
+ # @param type [Symbol] The namespace type (:xmi, :uml, etc.)
67
+ # @param version [String] The version string (e.g., "20131001")
68
+ # @return [String, nil] The full namespace URI
69
+ def self.build_namespace_uri(type, version)
70
+ case type
71
+ when :xmi
72
+ "http://www.omg.org/spec/XMI/#{version}"
73
+ when :uml
74
+ "http://www.omg.org/spec/UML/#{version}"
75
+ when :umldi
76
+ "http://www.omg.org/spec/UML/#{version}/UMLDI"
77
+ when :umldc
78
+ "http://www.omg.org/spec/UML/#{version}/UMLDC"
79
+ end
80
+ end
81
+
82
+ # Get detected namespace URIs for all detected versions
83
+ #
84
+ # @param xml_content [String] The XML content to parse
85
+ # @return [Hash] A hash with namespace types as keys and URIs as values
86
+ def self.detect_namespace_uris(xml_content)
87
+ versions = detect_versions(xml_content)
88
+ {
89
+ xmi: versions[:xmi] ? build_namespace_uri(:xmi, versions[:xmi]) : nil,
90
+ uml: versions[:uml] ? build_namespace_uri(:uml, versions[:uml]) : nil,
91
+ umldi: if versions[:umldi]
92
+ build_namespace_uri(:umldi,
93
+ versions[:umldi])
94
+ end,
95
+ umldc: if versions[:umldc]
96
+ build_namespace_uri(:umldc,
97
+ versions[:umldc])
98
+ end,
99
+ }
100
+ end
101
+
102
+ # Check if the XML uses a specific namespace version
103
+ #
104
+ # @param xml_content [String] The XML content to check
105
+ # @param type [Symbol] The namespace type
106
+ # @param version [String] The version to check for
107
+ # @return [Boolean] True if the XML uses the specified version
108
+ def self.uses_version?(xml_content, type, version)
109
+ detected = detect_versions(xml_content)
110
+ detected[type] == version
111
+ end
112
+
113
+ # Get a summary of all namespaces and their versions in the XML
114
+ #
115
+ # @param xml_content [String] The XML content to analyze
116
+ # @return [Hash] Detailed namespace information
117
+ def self.analyze(xml_content)
118
+ versions = detect_versions(xml_content)
119
+ uris = detect_namespace_uris(xml_content)
120
+ raw_namespaces = extract_namespace_uris(xml_content)
121
+
122
+ {
123
+ versions: versions,
124
+ uris: uris,
125
+ raw_namespaces: raw_namespaces,
126
+ normalized_needed: normalization_needed?(versions),
127
+ }
128
+ end
129
+
130
+ # Check if namespace normalization is needed
131
+ #
132
+ # @param versions [Hash] The detected versions hash
133
+ # @return [Boolean] True if any namespace is not 20131001
134
+ def self.normalization_needed?(versions)
135
+ versions.values.any? { |v| v && v != "20131001" }
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ # Registry for mapping namespace URIs to namespace classes
5
+ #
6
+ # This module provides a central registry for namespace classes,
7
+ # allowing dynamic lookup of namespace classes by URI.
8
+ module NamespaceRegistry
9
+ class << self
10
+ # Get the URI-to-class mapping
11
+ #
12
+ # @return [Hash<String, Class>] The mapping of URIs to namespace classes
13
+ def uri_map
14
+ @uri_map ||= {}
15
+ end
16
+
17
+ # Register a URI-to-class mapping
18
+ #
19
+ # @param uri [String] The namespace URI
20
+ # @param namespace_class [Class] The namespace class
21
+ def register(uri, namespace_class)
22
+ uri_map[uri] = namespace_class
23
+ end
24
+
25
+ # Resolve a namespace class by URI
26
+ #
27
+ # @param uri [String] The namespace URI to look up
28
+ # @return [Class, nil] The namespace class or nil if not found
29
+ def resolve(uri)
30
+ uri_map[uri]
31
+ end
32
+
33
+ # Check if a URI is registered
34
+ #
35
+ # @param uri [String] The namespace URI to check
36
+ # @return [Boolean] True if the URI is registered
37
+ def registered?(uri)
38
+ uri_map.key?(uri)
39
+ end
40
+
41
+ # Get all registered URIs
42
+ #
43
+ # @return [Array<String>] List of registered URIs
44
+ def registered_uris
45
+ uri_map.keys
46
+ end
47
+
48
+ # Clear the registry (mainly for testing)
49
+ def clear
50
+ @uri_map = {}
51
+ end
52
+
53
+ # Bootstrap the registry with all known OMG and Sparx namespaces
54
+ #
55
+ # This method registers all known namespace URIs from the
56
+ # Xmi::Namespace::Omg and Xmi::Namespace::Sparx modules.
57
+ def bootstrap!
58
+ register_omg_namespaces!
59
+ register_sparx_namespaces!
60
+ end
61
+
62
+ private
63
+
64
+ # Register all OMG namespace URIs
65
+ def register_omg_namespaces!
66
+ return unless defined?(::Xmi::Namespace::Omg)
67
+
68
+ # XMI namespaces
69
+ register("http://www.omg.org/spec/XMI/20110701",
70
+ ::Xmi::Namespace::Omg::Xmi20110701)
71
+ register("http://www.omg.org/spec/XMI/20131001",
72
+ ::Xmi::Namespace::Omg::Xmi20131001)
73
+ register("http://www.omg.org/spec/XMI/20161101",
74
+ ::Xmi::Namespace::Omg::Xmi20161101)
75
+
76
+ # UML namespaces
77
+ register("http://www.omg.org/spec/UML/20110701",
78
+ ::Xmi::Namespace::Omg::Uml20110701)
79
+ register("http://www.omg.org/spec/UML/20131001",
80
+ ::Xmi::Namespace::Omg::Uml20131001)
81
+ register("http://www.omg.org/spec/UML/20161101",
82
+ ::Xmi::Namespace::Omg::Uml20161101)
83
+
84
+ # UMLDI namespaces
85
+ register("http://www.omg.org/spec/UML/20131001/UMLDI",
86
+ ::Xmi::Namespace::Omg::UmlDi20131001)
87
+ register("http://www.omg.org/spec/UML/20161101/UMLDI",
88
+ ::Xmi::Namespace::Omg::UmlDi20161101)
89
+
90
+ # UMLDC namespaces
91
+ register("http://www.omg.org/spec/UML/20131001/UMLDC",
92
+ ::Xmi::Namespace::Omg::UmlDc20131001)
93
+ register("http://www.omg.org/spec/UML/20161101/UMLDC",
94
+ ::Xmi::Namespace::Omg::UmlDc20161101)
95
+ end
96
+
97
+ # Register all Sparx namespace URIs
98
+ def register_sparx_namespaces!
99
+ return unless defined?(::Xmi::Namespace::Sparx)
100
+
101
+ # Sparx profile namespaces
102
+ register("http://www.sparxsystems.com/profiles/SysPhS/1.0",
103
+ ::Xmi::Namespace::Sparx::SysPhS)
104
+ register("http://www.sparxsystems.com/profiles/GML/1.0",
105
+ ::Xmi::Namespace::Sparx::Gml)
106
+ register("http://www.sparxsystems.com/profiles/EAUML/1.0",
107
+ ::Xmi::Namespace::Sparx::EaUml)
108
+ register("http://www.sparxsystems.com/profiles/thecustomprofile/1.0",
109
+ ::Xmi::Namespace::Sparx::CustomProfile)
110
+ register("http://www.sparxsystems.com/profiles/CityGML/1.0",
111
+ ::Xmi::Namespace::Sparx::CityGml)
112
+
113
+ # Sparx Extension namespace (for EA-specific elements)
114
+ register("http://www.sparxsystems.com/extensions/ea",
115
+ ::Xmi::Namespace::Sparx::Extension)
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xmi
4
+ # Unified API for XMI parsing with version support
5
+ #
6
+ # This module provides a consistent interface for parsing XMI documents
7
+ # with automatic version detection or explicit version specification.
8
+ #
9
+ # @example
10
+ # # Auto-detect version from XML
11
+ # doc = Xmi::Parsing.parse(xml_content)
12
+ #
13
+ # @example
14
+ # # Force specific version
15
+ # doc = Xmi::Parsing.parse(xml_content, version: "20131001")
16
+ #
17
+ module Parsing
18
+ class << self
19
+ # @api public
20
+ # Parse XMI with automatic version detection
21
+ #
22
+ # @param xml [String, IO] XML content or stream
23
+ # @param options [Hash] Parsing options
24
+ # @option options [String] :version Force specific version
25
+ # @option options [Lutaml::Model::Register] :register Use specific register
26
+ # @option options [Class] :model_class Model class to parse into
27
+ # @option options [Boolean] :strict Raise on unknown elements
28
+ # @return [Root, Object] Parsed XMI document
29
+ def parse(xml, options = {})
30
+ xml_content = xml.respond_to?(:read) ? xml.read : xml.to_s
31
+
32
+ Xmi.init_versioning! unless Xmi.versioning_initialized?
33
+
34
+ register = determine_register(xml_content, options)
35
+ model_class = options[:model_class] || Root
36
+
37
+ if register
38
+ model_class.from_xml(xml_content, register: register)
39
+ else
40
+ # Fallback to default parsing (existing behavior)
41
+ model_class.from_xml(xml_content)
42
+ end
43
+ end
44
+
45
+ # @api public
46
+ # Parse XMI file
47
+ #
48
+ # @param path [String] File path
49
+ # @param options [Hash] Parsing options
50
+ # @return [Root, Object] Parsed XMI document
51
+ def parse_file(path, options = {})
52
+ parse(File.read(path), options)
53
+ end
54
+
55
+ # @api public
56
+ # Detect XMI version without full parsing
57
+ #
58
+ # @param xml [String] XML content
59
+ # @return [Hash] Version information with :versions and :uris keys
60
+ def detect_version(xml)
61
+ versions = NamespaceDetector.detect_versions(xml)
62
+ uris = NamespaceDetector.detect_namespace_uris(xml)
63
+
64
+ {
65
+ versions: versions,
66
+ uris: uris,
67
+ xmi_version: versions[:xmi],
68
+ uml_version: versions[:uml],
69
+ }
70
+ end
71
+
72
+ # @api public
73
+ # Get supported XMI versions
74
+ #
75
+ # @return [Array<String>]
76
+ def supported_versions
77
+ VersionRegistry.available_versions
78
+ end
79
+
80
+ # @api public
81
+ # Check if a version is supported
82
+ #
83
+ # @param version [String] Version string (e.g., "20131001")
84
+ # @return [Boolean]
85
+ def version_supported?(version)
86
+ supported_versions.include?(version)
87
+ end
88
+
89
+ private
90
+
91
+ # Determine the appropriate register for parsing
92
+ #
93
+ # @param xml_content [String] XML content
94
+ # @param options [Hash] Options hash
95
+ # @return [Lutaml::Model::Register, nil]
96
+ def determine_register(xml_content, options)
97
+ # Explicit register takes precedence
98
+ return options[:register] if options[:register]
99
+
100
+ # Explicit version
101
+ if options[:version]
102
+ reg = VersionRegistry.register_for_version(options[:version])
103
+ raise ArgumentError, "Unknown version: #{options[:version]}" unless reg
104
+
105
+ return reg
106
+ end
107
+
108
+ # Auto-detect from XML content
109
+ VersionRegistry.detect_register(xml_content)
110
+ end
111
+ end
112
+ end
113
+ end
data/lib/xmi/replace.rb CHANGED
@@ -5,12 +5,12 @@ require_relative "extension"
5
5
 
6
6
  module Xmi
7
7
  class Replace < Lutaml::Model::Serializable
8
- attribute :id, :string
9
- attribute :label, :string
10
- attribute :uuid, :string
8
+ attribute :id, ::Xmi::Type::XmiId
9
+ attribute :label, ::Xmi::Type::XmiLabel
10
+ attribute :uuid, ::Xmi::Type::XmiUuid
11
11
  attribute :href, :string
12
- attribute :idref, :string
13
- attribute :type, :string
12
+ attribute :idref, ::Xmi::Type::XmiIdRef
13
+ attribute :type, ::Xmi::Type::XmiType
14
14
  attribute :target, :string
15
15
  attribute :container, :string
16
16
  attribute :position, :integer
@@ -18,46 +18,22 @@ module Xmi
18
18
  attribute :difference, Difference, collection: true
19
19
  attribute :extension, Extension, collection: true
20
20
 
21
- xml do # rubocop:disable Metrics/BlockLength
21
+ xml do
22
22
  root "Replace"
23
- namespace "http://www.omg.org/spec/XMI/20131001", "xmlns"
23
+ namespace ::Xmi::Namespace::Omg::Xmi
24
24
 
25
- map_attribute "id", to: :id, prefix: "xmlns", namespace: "http://www.omg.org/spec/XMI/20131001"
26
- map_attribute "label", to: :label, prefix: "xmlns", namespace: "http://www.omg.org/spec/XMI/20131001"
27
- map_attribute "uuid", to: :uuid, prefix: "xmlns", namespace: "http://www.omg.org/spec/XMI/20131001"
25
+ map_attribute "id", to: :id
26
+ map_attribute "label", to: :label
27
+ map_attribute "uuid", to: :uuid
28
28
  map_attribute "href", to: :href
29
- map_attribute "idref", to: :idref, prefix: "xmlns", namespace: "http://www.omg.org/spec/XMI/20131001"
30
- map_attribute "type", to: :type, prefix: "xmlns", namespace: "http://www.omg.org/spec/XMI/20131001"
29
+ map_attribute "idref", to: :idref
30
+ map_attribute "type", to: :type
31
31
  map_attribute "target", to: :target
32
32
  map_attribute "container", to: :container
33
33
  map_attribute "position", to: :position
34
34
  map_attribute "replacement", to: :replacement
35
- map_element "difference", to: :difference, prefix: nil, namespace: nil,
36
- value_map: {
37
- from: {
38
- nil: :empty,
39
- empty: :empty,
40
- omitted: :empty
41
- },
42
- to: {
43
- nil: :empty,
44
- empty: :empty,
45
- omitted: :empty
46
- }
47
- }
48
- map_element "Extension", to: :extension,
49
- value_map: {
50
- from: {
51
- nil: :empty,
52
- empty: :empty,
53
- omitted: :empty
54
- },
55
- to: {
56
- nil: :empty,
57
- empty: :empty,
58
- omitted: :empty
59
- }
60
- }
35
+ map_element "difference", to: :difference, value_map: VALUE_MAP
36
+ map_element "Extension", to: :extension, value_map: VALUE_MAP
61
37
  end
62
38
  end
63
39
  end