ea 0.6.130 → 0.6.131
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/lib/ea/sources/xmi/adapter.rb +33 -10
- data/lib/ea/sources/xmi/annotation_builder.rb +8 -0
- data/lib/ea/sources/xmi/classifier_builder.rb +11 -1
- data/lib/ea/sources/xmi/extension_style_parser.rb +6 -5
- data/lib/ea/sources/xmi/extension_types.rb +59 -0
- data/lib/ea/sources/xmi/instance_builder.rb +98 -0
- data/lib/ea/sources/xmi/operation_builder.rb +5 -5
- data/lib/ea/sources/xmi/reception_builder.rb +59 -0
- data/lib/ea/sources/xmi/relationship_builder.rb +60 -0
- data/lib/ea/sources/xmi/type_classifier_map.rb +1 -0
- data/lib/ea/sources/xmi/umldi_keyword_extractor.rb +10 -22
- data/lib/ea/sources/xmi.rb +3 -0
- data/lib/ea/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1ecc752b8f42a0d41b86681bc355d5f76e87c656ec5f18c2beaeffbbabe6f990
|
|
4
|
+
data.tar.gz: a5d37a462b9f90e594243b2d0deb2b1692866a846a9e3a57423c6ebd41ea7a13
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 011a8bf645f17a054db1fc45e8b078a5436bf8dc57fa27c27a3c91fd8a34178c9f22722ceed040893417335ec472e9f751656be2d471c9f9e252b7ca882a3c44
|
|
7
|
+
data.tar.gz: 051b2492e102e467b7ea3f6a59edb1a3981ef7579d80fd51a23aaf815434ba90635d155a8c024cfad4d082cf006ae41c33387a2fac2cf9f5188cdc9d6fcefa11
|
|
@@ -32,9 +32,11 @@ module Ea
|
|
|
32
32
|
relationships: relationships,
|
|
33
33
|
stereotypes: [],
|
|
34
34
|
notes: notes,
|
|
35
|
+
instance_specifications: instance_specifications,
|
|
35
36
|
diagrams: diagrams
|
|
36
37
|
)
|
|
37
38
|
apply_tagged_values(doc)
|
|
39
|
+
apply_receptions(doc)
|
|
38
40
|
apply_stereotypes(doc)
|
|
39
41
|
resolve_type_names(doc)
|
|
40
42
|
doc
|
|
@@ -42,6 +44,13 @@ module Ea
|
|
|
42
44
|
|
|
43
45
|
private
|
|
44
46
|
|
|
47
|
+
def apply_receptions(doc)
|
|
48
|
+
grouped = ReceptionBuilder.new(xmi_path).grouped_by_owner
|
|
49
|
+
doc.classifiers.each do |classifier|
|
|
50
|
+
classifier.operations.concat(grouped.fetch(classifier.id, []))
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
45
54
|
# Attach stereotypes parsed from <xmi:Extension>/<elements>
|
|
46
55
|
# to their owning classifiers and packages.
|
|
47
56
|
def apply_stereotypes(doc)
|
|
@@ -90,27 +99,37 @@ module Ea
|
|
|
90
99
|
end
|
|
91
100
|
end
|
|
92
101
|
|
|
102
|
+
# Replaces each type reference with its in-model qualified
|
|
103
|
+
# name; references that are not classifiers (primitive hrefs,
|
|
104
|
+
# EA placeholder idrefs) take the type name EA stores in the
|
|
105
|
+
# extension block.
|
|
93
106
|
def resolve_type_names(doc)
|
|
94
|
-
# In the XMI, the <type xmi:idref=...> is the type
|
|
95
|
-
# classifier's GUID. The property's type_name was
|
|
96
|
-
# pre-populated by PropertyBuilder with the same idref;
|
|
97
|
-
# we replace it with the in-model qualified name when
|
|
98
|
-
# the referenced classifier is loaded.
|
|
99
107
|
qualified_by_id = doc.classifiers.each_with_object({}) do |c, acc|
|
|
100
108
|
acc[c.id] = c.qualified_name if c.id && c.qualified_name
|
|
101
109
|
end
|
|
110
|
+
types = ExtensionTypes.from_path(xmi_path)
|
|
102
111
|
|
|
103
112
|
doc.classifiers.each do |classifier|
|
|
104
113
|
(classifier.properties || []).each do |prop|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
114
|
+
prop.type_name = qualified_by_id[prop.type_ref] ||
|
|
115
|
+
types.attribute_type(prop.id) || prop.type_name
|
|
116
|
+
end
|
|
117
|
+
(classifier.operations || []).each do |op|
|
|
118
|
+
resolve_operation_types(op, qualified_by_id, types)
|
|
110
119
|
end
|
|
111
120
|
end
|
|
112
121
|
end
|
|
113
122
|
|
|
123
|
+
def resolve_operation_types(operation, qualified_by_id, types)
|
|
124
|
+
operation.return_type_name = qualified_by_id[operation.return_type_name] ||
|
|
125
|
+
types.return_type(operation.id) ||
|
|
126
|
+
operation.return_type_name
|
|
127
|
+
operation.parameters.each do |param|
|
|
128
|
+
param.type_name = qualified_by_id[param.type_name] ||
|
|
129
|
+
types.parameter_type(param.id) || param.type_name
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
114
133
|
def metadata
|
|
115
134
|
@metadata ||= MetadataBuilder.new(root, xmi_path).build
|
|
116
135
|
end
|
|
@@ -123,6 +142,10 @@ module Ea
|
|
|
123
142
|
@classifiers ||= ClassifierBuilder.new(root).build_all
|
|
124
143
|
end
|
|
125
144
|
|
|
145
|
+
def instance_specifications
|
|
146
|
+
@instance_specifications ||= InstanceBuilder.new(root, xmi_path, classifiers).build_all
|
|
147
|
+
end
|
|
148
|
+
|
|
126
149
|
def relationships
|
|
127
150
|
@relationships ||= RelationshipBuilder.new(root).build_all
|
|
128
151
|
end
|
|
@@ -22,6 +22,14 @@ module Ea
|
|
|
22
22
|
from_comments(comments, owner_id, kind: kind)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# Documentation text from the element's Extension properties,
|
|
26
|
+
# the XMI counterpart of an EA element note.
|
|
27
|
+
def self.from_documentation(text, owner_id, kind: "documentation")
|
|
28
|
+
return [] if text.nil? || text.empty?
|
|
29
|
+
|
|
30
|
+
[new(owner_id).build(kind, text)]
|
|
31
|
+
end
|
|
32
|
+
|
|
25
33
|
def self.from_comments(comments, owner_id, kind: "documentation")
|
|
26
34
|
Array(comments).map { |c| new(owner_id).build(kind, c.body_element || c.body_attribute) }
|
|
27
35
|
end
|
|
@@ -55,10 +55,20 @@ module Ea
|
|
|
55
55
|
visibility: element.visibility,
|
|
56
56
|
properties: PropertyBuilder.new.build_all_for(element),
|
|
57
57
|
operations: OperationBuilder.new.build_all_for(element),
|
|
58
|
-
annotations:
|
|
58
|
+
annotations: annotations_for(element, id)
|
|
59
59
|
}
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
+
def annotations_for(element, id)
|
|
63
|
+
AnnotationBuilder.from_element(element, id) +
|
|
64
|
+
AnnotationBuilder.from_documentation(documentation_by_id[id], id)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def documentation_by_id
|
|
68
|
+
@documentation_by_id ||=
|
|
69
|
+
(root.extension&.elements&.element || []).to_h { |e| [e.idref, e.properties&.documentation] }
|
|
70
|
+
end
|
|
71
|
+
|
|
62
72
|
def enumeration_args(element)
|
|
63
73
|
literals = element.owned_literal.map.with_index do |lit, idx|
|
|
64
74
|
Ea::Model::EnumerationLiteral.new(
|
|
@@ -67,13 +67,14 @@ module Ea
|
|
|
67
67
|
value == "1" || value == "true" || value == "-1"
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
+
# fontsz is EA's tenths of a point, rendered through GDI's double
|
|
71
|
+
# rounding (pixels = floor(tenths * 96 / 720), points =
|
|
72
|
+
# floor(pixels * 72 / 96)). Absent or zero means inherit, as nil.
|
|
70
73
|
def scale_font_size(value)
|
|
71
|
-
|
|
74
|
+
tenths = int(value)
|
|
75
|
+
return nil if tenths.nil? || tenths.zero?
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
return 13 if pct.nil? || pct.zero?
|
|
75
|
-
|
|
76
|
-
(pct * 13 / 100).round
|
|
77
|
+
(tenths * 96 / 720 * 72 / 96.0).floor
|
|
77
78
|
end
|
|
78
79
|
|
|
79
80
|
Style = Struct.new(
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
|
|
5
|
+
module Ea
|
|
6
|
+
module Sources
|
|
7
|
+
module Xmi
|
|
8
|
+
# Type names EA stores in the <xmi:Extension><elements> block,
|
|
9
|
+
# where the UML side only carries a primitive-type href or a
|
|
10
|
+
# placeholder idref. The xmi gem does not expose operations or
|
|
11
|
+
# parameters of the extension, so the raw XML is read once.
|
|
12
|
+
#
|
|
13
|
+
# <attribute xmi:idref="ID"><properties type="int"/>
|
|
14
|
+
# <operation xmi:idref="ID"><type type="void"/>
|
|
15
|
+
# <parameter xmi:idref="ID"><properties type="int"/>
|
|
16
|
+
class ExtensionTypes
|
|
17
|
+
def self.from_path(xmi_path)
|
|
18
|
+
return empty if xmi_path.nil?
|
|
19
|
+
|
|
20
|
+
new(Nokogiri::XML(File.read(xmi_path)))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.empty
|
|
24
|
+
new(nil)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def initialize(document)
|
|
28
|
+
@attributes = index(document, "attribute", "properties")
|
|
29
|
+
@returns = index(document, "operation", "type")
|
|
30
|
+
@parameters = index(document, "parameter", "properties")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def attribute_type(id)
|
|
34
|
+
@attributes[id]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def return_type(operation_id)
|
|
38
|
+
@returns[operation_id]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def parameter_type(parameter_id)
|
|
42
|
+
@parameters[parameter_id]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def index(document, owner, child)
|
|
48
|
+
return {} unless document
|
|
49
|
+
|
|
50
|
+
document.xpath(%(//*[local-name()="#{owner}"][@*[local-name()="idref"]])).each_with_object({}) do |node, acc|
|
|
51
|
+
type = node.at_xpath(%(*[local-name()="#{child}"]/@type))&.value
|
|
52
|
+
idref = node.at_xpath("@*[local-name()='idref']").value
|
|
53
|
+
acc[idref] = type unless type.nil? || type.empty?
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
|
|
5
|
+
module Ea
|
|
6
|
+
module Sources
|
|
7
|
+
module Xmi
|
|
8
|
+
# Builds Ea::Model::InstanceSpecification objects from
|
|
9
|
+
# uml:InstanceSpecification packaged elements. Slots come from
|
|
10
|
+
# the `runstate` attribute of the element's Extension entry,
|
|
11
|
+
# which carries slot names and operators that the typed
|
|
12
|
+
# `<slot>` children omit.
|
|
13
|
+
class InstanceBuilder
|
|
14
|
+
XMI_TYPE = "uml:InstanceSpecification"
|
|
15
|
+
RUNSTATE_XPATH =
|
|
16
|
+
%(//*[local-name()="element"]/*[local-name()="extendedProperties"][@runstate])
|
|
17
|
+
|
|
18
|
+
attr_reader :root, :xmi_path, :classifiers
|
|
19
|
+
|
|
20
|
+
def initialize(root, xmi_path, classifiers)
|
|
21
|
+
@root = root
|
|
22
|
+
@xmi_path = xmi_path
|
|
23
|
+
@classifiers = classifiers
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def build_all
|
|
27
|
+
instances = []
|
|
28
|
+
walk(root.model) do |element, package|
|
|
29
|
+
instances << build_one(element, package) if element.type == XMI_TYPE
|
|
30
|
+
end
|
|
31
|
+
instances
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def walk(element, package = nil, &block)
|
|
37
|
+
yield element, package
|
|
38
|
+
element.packaged_element.each do |inner|
|
|
39
|
+
walk(inner, inner.type == "uml:Package" ? inner : package, &block)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def build_one(element, package)
|
|
44
|
+
id = IdNormalizer.from_xmi_id(element.id)
|
|
45
|
+
properties = extension_properties[id]
|
|
46
|
+
Ea::Model::InstanceSpecification.new(
|
|
47
|
+
id: id,
|
|
48
|
+
name: element.name.to_s,
|
|
49
|
+
role_name: properties&.alias,
|
|
50
|
+
classifier_id: IdNormalizer.from_xmi_id(element.classifier),
|
|
51
|
+
classifier_name: classifier_names[element.classifier],
|
|
52
|
+
stereotype: properties&.stereotype.to_s,
|
|
53
|
+
package_id: package && IdNormalizer.from_xmi_id(package.id),
|
|
54
|
+
package_name: package&.name,
|
|
55
|
+
qualified_name: qualified_name(element, package),
|
|
56
|
+
slots: slots_for(id),
|
|
57
|
+
annotations: AnnotationBuilder.from_element(element, id) +
|
|
58
|
+
AnnotationBuilder.from_documentation(properties&.documentation, id)
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def qualified_name(element, package)
|
|
63
|
+
name = element.name.to_s
|
|
64
|
+
return name if name.empty? || package.nil?
|
|
65
|
+
|
|
66
|
+
"#{package.name}::#{name}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def classifier_names
|
|
70
|
+
@classifier_names ||= classifiers.to_h { |c| [c.id, c.name.to_s] }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def extension_properties
|
|
74
|
+
@extension_properties ||=
|
|
75
|
+
(root.extension&.elements&.element || []).to_h { |e| [e.idref, e.properties] }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def slots_for(id)
|
|
79
|
+
runstates[id].to_s.scan(Ea::Sources::Qea::InstanceBuilder::RUN_STATE_PATTERN).map do |(name, value, op)|
|
|
80
|
+
Ea::Model::Slot.new(name: name.to_s, value: value.to_s, op: op.to_s.empty? ? "=" : op)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def runstates
|
|
85
|
+
@runstates ||= extension_runstates
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def extension_runstates
|
|
89
|
+
return {} if xmi_path.nil?
|
|
90
|
+
|
|
91
|
+
Nokogiri::XML(File.read(xmi_path))
|
|
92
|
+
.xpath(RUNSTATE_XPATH)
|
|
93
|
+
.to_h { |node| [node.parent["xmi:idref"], node["runstate"]] }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -19,7 +19,7 @@ module Ea
|
|
|
19
19
|
owner_id: owner.id,
|
|
20
20
|
qualified_name: "#{owner.name}::#{op.name}",
|
|
21
21
|
return_type_name: return_type_name_for(op),
|
|
22
|
-
visibility: op.visibility,
|
|
22
|
+
visibility: op.visibility || "public",
|
|
23
23
|
is_static: boolean(op.is_static),
|
|
24
24
|
is_abstract: boolean(op.is_abstract),
|
|
25
25
|
parameters: build_parameters(op, id),
|
|
@@ -30,21 +30,21 @@ module Ea
|
|
|
30
30
|
private
|
|
31
31
|
|
|
32
32
|
def build_parameters(op, owner_op_id)
|
|
33
|
-
op.owned_parameter.map.with_index do |param, idx|
|
|
34
|
-
direction = param.direction ||
|
|
33
|
+
op.owned_parameter.reject { |p| p.direction == "return" }.map.with_index do |param, idx|
|
|
34
|
+
direction = param.direction || "in"
|
|
35
35
|
Ea::Model::Parameter.new(
|
|
36
36
|
id: IdNormalizer.from_xmi_id(param.id) ||
|
|
37
37
|
IdNormalizer.synthetic_id(owner_op_id, "param", idx.to_s),
|
|
38
38
|
name: param.name || direction,
|
|
39
39
|
ordinal: idx,
|
|
40
40
|
direction: direction,
|
|
41
|
-
type_name: param.
|
|
41
|
+
type_name: param.classifier_type
|
|
42
42
|
)
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def return_type_name_for(op)
|
|
47
|
-
op.owned_parameter.find { |p| p.direction == "return" }&.
|
|
47
|
+
op.owned_parameter.find { |p| p.direction == "return" }&.classifier_type
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def boolean(value)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
|
|
5
|
+
module Ea
|
|
6
|
+
module Sources
|
|
7
|
+
module Xmi
|
|
8
|
+
# The xmi gem omits ownedReception, so read those source members
|
|
9
|
+
# directly rather than inferring operations from extension rows.
|
|
10
|
+
class ReceptionBuilder
|
|
11
|
+
def initialize(xmi_path)
|
|
12
|
+
@xmi_path = xmi_path
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def grouped_by_owner
|
|
16
|
+
return {} unless @xmi_path
|
|
17
|
+
|
|
18
|
+
@grouped_by_owner ||= read_receptions(Nokogiri::XML(File.read(@xmi_path)))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def read_receptions(document)
|
|
24
|
+
scopes = extension_scopes(document)
|
|
25
|
+
xpath = '//*[local-name()="Model"]//*[local-name()="packagedElement"]/*[local-name()="ownedReception"]'
|
|
26
|
+
document.xpath(xpath).each_with_object({}) do |node, grouped|
|
|
27
|
+
owner_id = xmi_attribute(node.parent, "id")
|
|
28
|
+
next unless owner_id
|
|
29
|
+
|
|
30
|
+
(grouped[owner_id] ||= []) << build_one(node, owner_id, scopes)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def build_one(node, owner_id, scopes)
|
|
35
|
+
id = xmi_attribute(node, "id") ||
|
|
36
|
+
IdNormalizer.synthetic_id(owner_id, "reception", node["name"])
|
|
37
|
+
Ea::Model::Operation.new(
|
|
38
|
+
id: id, name: node["name"], owner_id: owner_id,
|
|
39
|
+
qualified_name: "#{node.parent["name"]}::#{node["name"]}",
|
|
40
|
+
is_reception: true,
|
|
41
|
+
visibility: node["visibility"] || scopes[id] || "public"
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def extension_scopes(document)
|
|
46
|
+
xpath = '//*[local-name()="Extension"]//*[local-name()="receptions"]/*[local-name()="reception"]'
|
|
47
|
+
document.xpath(xpath).each_with_object({}) do |node, scopes|
|
|
48
|
+
id = xmi_attribute(node, "idref")
|
|
49
|
+
scopes[id] = node["scope"].downcase if id && node["scope"]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def xmi_attribute(node, name)
|
|
54
|
+
node.at_xpath("@*[local-name()='#{name}']")&.value
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -7,6 +7,13 @@ module Ea
|
|
|
7
7
|
# uml:Generalization, uml:Realization, uml:Dependency) into
|
|
8
8
|
# the appropriate Ea::Model::Relationship subclass.
|
|
9
9
|
class RelationshipBuilder
|
|
10
|
+
# Connector types that imply an aggregation on the source end
|
|
11
|
+
# when the end itself records none.
|
|
12
|
+
AGGREGATION_FOR_CONNECTOR_TYPE = {
|
|
13
|
+
"Aggregation" => "shared",
|
|
14
|
+
"Composition" => "composite"
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
10
17
|
attr_reader :root
|
|
11
18
|
|
|
12
19
|
def initialize(root)
|
|
@@ -59,6 +66,41 @@ module Ea
|
|
|
59
66
|
end
|
|
60
67
|
|
|
61
68
|
def association_args(element)
|
|
69
|
+
connector = connector_index[element.id]
|
|
70
|
+
return owned_end_args(element) unless connector&.source && connector.target
|
|
71
|
+
|
|
72
|
+
connector_end_args(element, connector)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def connector_end_args(element, connector)
|
|
76
|
+
source = connector.source
|
|
77
|
+
target = connector.target
|
|
78
|
+
{
|
|
79
|
+
source_id: source.idref,
|
|
80
|
+
target_id: target.idref,
|
|
81
|
+
source_role_name: source.role&.name,
|
|
82
|
+
target_role_name: target.role&.name,
|
|
83
|
+
source_multiplicity_lower: card_lower(source.type&.multiplicity),
|
|
84
|
+
source_multiplicity_upper: card_upper(source.type&.multiplicity),
|
|
85
|
+
target_multiplicity_lower: card_lower(target.type&.multiplicity),
|
|
86
|
+
target_multiplicity_upper: card_upper(target.type&.multiplicity),
|
|
87
|
+
source_multiplicity_raw: raw_card(source.type&.multiplicity),
|
|
88
|
+
target_multiplicity_raw: raw_card(target.type&.multiplicity),
|
|
89
|
+
source_aggregation: source_aggregation(connector),
|
|
90
|
+
target_aggregation: target.type&.aggregation || "none",
|
|
91
|
+
source_navigable: navigable?(element, :source),
|
|
92
|
+
target_navigable: navigable?(element, :target)
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def source_aggregation(connector)
|
|
97
|
+
flagged = connector.source.type&.aggregation || "none"
|
|
98
|
+
return flagged unless flagged == "none"
|
|
99
|
+
|
|
100
|
+
AGGREGATION_FOR_CONNECTOR_TYPE.fetch(connector.properties&.ea_type, "none")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def owned_end_args(element)
|
|
62
104
|
ends = element.member_ends.to_a
|
|
63
105
|
source_id = ends.first&.idref
|
|
64
106
|
target_id = ends.last&.idref
|
|
@@ -81,6 +123,24 @@ module Ea
|
|
|
81
123
|
}
|
|
82
124
|
end
|
|
83
125
|
|
|
126
|
+
def raw_card(cardinality)
|
|
127
|
+
text = cardinality.to_s.strip
|
|
128
|
+
text.empty? ? nil : text
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def card_lower(cardinality)
|
|
132
|
+
text = raw_card(cardinality)
|
|
133
|
+
Integer(text.split("..").first, exception: false) if text
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def card_upper(cardinality)
|
|
137
|
+
text = raw_card(cardinality)
|
|
138
|
+
return nil unless text
|
|
139
|
+
|
|
140
|
+
upper = text.split("..").last
|
|
141
|
+
upper == "*" ? -1 : Integer(upper, exception: false)
|
|
142
|
+
end
|
|
143
|
+
|
|
84
144
|
def generalization_args(element)
|
|
85
145
|
# PackagedElement#generalization is a collection of
|
|
86
146
|
# AssociationGeneralization rows. The "specific" (child)
|
|
@@ -40,6 +40,14 @@ module Ea
|
|
|
40
40
|
|
|
41
41
|
private
|
|
42
42
|
|
|
43
|
+
# Text of the shape's UMLKeywordLabel child; name labels and
|
|
44
|
+
# other labelled children never contribute a keyword.
|
|
45
|
+
def keyword_text(shape)
|
|
46
|
+
label = shape.xpath("./ownedElement[@*[local-name()=\"type\"]=\"umldi:UMLKeywordLabel\"]").first
|
|
47
|
+
text = label && label["text"]
|
|
48
|
+
text unless text.nil? || text.empty?
|
|
49
|
+
end
|
|
50
|
+
|
|
43
51
|
def doc
|
|
44
52
|
@doc ||= Nokogiri::XML(File.read(xmi_path))
|
|
45
53
|
end
|
|
@@ -47,34 +55,14 @@ module Ea
|
|
|
47
55
|
def keywords_by_diagram
|
|
48
56
|
@keywords_by_diagram ||= begin
|
|
49
57
|
result = {}
|
|
50
|
-
# The Sparx XMI export declares the umldi: namespace
|
|
51
|
-
# but the elements inside Diagram have lost their
|
|
52
|
-
# prefix binding once parsed by Nokogiri — the
|
|
53
|
-
# element name resolves to local-name "ownedElement"
|
|
54
|
-
# and the umldi:UMLClassifierShape type information
|
|
55
|
-
# lives in the literal `type` attribute. We therefore
|
|
56
|
-
# match shape children by the presence of `modelElement`
|
|
57
|
-
# and the keyword label by its `text` attribute.
|
|
58
58
|
doc.xpath("//*[local-name()=\"Diagram\"]").each do |diagram|
|
|
59
59
|
diagram_id = diagram["id"] || diagram["xmi:id"]
|
|
60
60
|
next unless diagram_id
|
|
61
61
|
|
|
62
62
|
keywords = {}
|
|
63
63
|
diagram.xpath("./ownedElement[@modelElement]").each do |shape|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
# Keyword label is the first ownedElement child
|
|
68
|
-
# with a non-empty `text` attribute. The name
|
|
69
|
-
# label follows it.
|
|
70
|
-
shape.xpath("./ownedElement[@text]").each do |child|
|
|
71
|
-
text = child["text"]
|
|
72
|
-
next if text.nil? || text.empty?
|
|
73
|
-
next if text.start_with?("«") # safety
|
|
74
|
-
|
|
75
|
-
keywords[model_id] = text
|
|
76
|
-
break
|
|
77
|
-
end
|
|
64
|
+
text = keyword_text(shape)
|
|
65
|
+
keywords[shape["modelElement"]] = text if text
|
|
78
66
|
end
|
|
79
67
|
result[diagram_id] = keywords unless keywords.empty?
|
|
80
68
|
end
|
data/lib/ea/sources/xmi.rb
CHANGED
|
@@ -18,14 +18,17 @@ module Ea
|
|
|
18
18
|
autoload :MetadataBuilder, "ea/sources/xmi/metadata_builder"
|
|
19
19
|
autoload :PackageBuilder, "ea/sources/xmi/package_builder"
|
|
20
20
|
autoload :ClassifierBuilder, "ea/sources/xmi/classifier_builder"
|
|
21
|
+
autoload :InstanceBuilder, "ea/sources/xmi/instance_builder"
|
|
21
22
|
autoload :PropertyBuilder, "ea/sources/xmi/property_builder"
|
|
22
23
|
autoload :OperationBuilder, "ea/sources/xmi/operation_builder"
|
|
24
|
+
autoload :ReceptionBuilder, "ea/sources/xmi/reception_builder"
|
|
23
25
|
autoload :RelationshipBuilder, "ea/sources/xmi/relationship_builder"
|
|
24
26
|
autoload :AnnotationBuilder, "ea/sources/xmi/annotation_builder"
|
|
25
27
|
autoload :TagBuilder, "ea/sources/xmi/tag_builder"
|
|
26
28
|
autoload :NoteBuilder, "ea/sources/xmi/note_builder"
|
|
27
29
|
autoload :StereotypeBuilder, "ea/sources/xmi/stereotype_builder"
|
|
28
30
|
autoload :DiagramBuilder, "ea/sources/xmi/diagram_builder"
|
|
31
|
+
autoload :ExtensionTypes, "ea/sources/xmi/extension_types"
|
|
29
32
|
autoload :ExtensionElements, "ea/sources/xmi/extension_elements"
|
|
30
33
|
autoload :ExtensionGeometryParser, "ea/sources/xmi/extension_geometry_parser"
|
|
31
34
|
autoload :ExtensionStyleParser, "ea/sources/xmi/extension_style_parser"
|
data/lib/ea/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ea
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.131
|
|
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-10-
|
|
11
|
+
date: 2026-10-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: lutaml-model
|
|
@@ -853,12 +853,15 @@ files:
|
|
|
853
853
|
- lib/ea/sources/xmi/extension_elements.rb
|
|
854
854
|
- lib/ea/sources/xmi/extension_geometry_parser.rb
|
|
855
855
|
- lib/ea/sources/xmi/extension_style_parser.rb
|
|
856
|
+
- lib/ea/sources/xmi/extension_types.rb
|
|
856
857
|
- lib/ea/sources/xmi/id_normalizer.rb
|
|
858
|
+
- lib/ea/sources/xmi/instance_builder.rb
|
|
857
859
|
- lib/ea/sources/xmi/metadata_builder.rb
|
|
858
860
|
- lib/ea/sources/xmi/note_builder.rb
|
|
859
861
|
- lib/ea/sources/xmi/operation_builder.rb
|
|
860
862
|
- lib/ea/sources/xmi/package_builder.rb
|
|
861
863
|
- lib/ea/sources/xmi/property_builder.rb
|
|
864
|
+
- lib/ea/sources/xmi/reception_builder.rb
|
|
862
865
|
- lib/ea/sources/xmi/relationship_builder.rb
|
|
863
866
|
- lib/ea/sources/xmi/relationship_type_map.rb
|
|
864
867
|
- lib/ea/sources/xmi/stereotype_builder.rb
|