ea 0.6.133 → 0.6.134

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: 020f344524c3f0702e581b349d7cb3f93a7069a3179684ca59440fd4d4762491
4
- data.tar.gz: 46f2d46ab28ba95e5c975fedfa9bcd176c19b2acc4f3803635786f070dd315c2
3
+ metadata.gz: 3320f99ef26e1639eaa88eba212fe6620cc7c25b5337681431bf1ba991c384c1
4
+ data.tar.gz: 3a72b42b4c3dd40b4254ec74684b3e74a25e6f35f1efc85ec8e7bb1fc59f113b
5
5
  SHA512:
6
- metadata.gz: 16f49f774901e21cee845099df07ff8adc9989c3679c03fb49b97692356a81353d40c73c1cdde5dddfcd74b49657744695b61dbf557c96aabebe730fa4f0a7c4
7
- data.tar.gz: 77fbf77c4cc929cb5565f2a9f712275b13e7244591b2dea3245d4ad096889531af5782aa4a8ad4888d9a2944e789fa9edd2657eb11cc5c93c5c6e9f17a7df3a0
6
+ metadata.gz: 8ce24e25f39d48215c9dcbe03b8c86a932ffd310d64f228e6e5603448814faf84fc023f1a07dae736f4dabb926970b86d1bd478e51d640366462c1765a643796
7
+ data.tar.gz: 19a7f0f2e20a3985c32de44288755f7daa7f5f35e41908c6491a9ada31562059c20826e75d7482ad494b8e9c2e15ab32f5635f1801607c359f7fcdc6a455ea50
@@ -58,11 +58,8 @@ module Ea
58
58
  # `model_element_ref` lookups from diagram elements resolve.
59
59
  def index_by_id
60
60
  @index_by_id ||= begin
61
- idx = {}
62
- packages.each do |p|
63
- idx[p.id] = p
64
- alias_eaid_for_package(idx, p)
65
- end
61
+ idx = PackageAliasIndex.new
62
+ packages.each { |p| idx[p.id] = p }
66
63
  classifiers.each { |c| idx[c.id] = c }
67
64
  relationships.each { |r| idx[r.id] = r }
68
65
  stereotypes.each { |s| idx[s.id] = s }
@@ -98,12 +95,6 @@ module Ea
98
95
  property_index[id]
99
96
  end
100
97
 
101
- def alias_eaid_for_package(idx, package)
102
- return unless package.id&.start_with?("EAPK_")
103
-
104
- idx["EAID_#{package.id[5..]}"] = package
105
- end
106
-
107
98
  def root_packages
108
99
  packages.select { |p| p.parent_id.nil? }
109
100
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Model
5
+ # Id-keyed lookup in which a package stored under `EAPK_<guid>`
6
+ # also answers to `EAID_<guid>`, without a second entry: `values`
7
+ # lists each package once.
8
+ class PackageAliasIndex < Hash
9
+ def [](id)
10
+ super || package_alias(id)
11
+ end
12
+
13
+ private
14
+
15
+ def package_alias(id)
16
+ return unless id.is_a?(String) && id.start_with?("EAID_")
17
+
18
+ fetch("EAPK_#{id[5..]}", nil)
19
+ end
20
+ end
21
+ end
22
+ end
data/lib/ea/model.rb CHANGED
@@ -19,6 +19,7 @@ module Ea
19
19
  autoload :Base, "ea/model/base"
20
20
  autoload :Metadata, "ea/model/metadata"
21
21
  autoload :Point, "ea/model/point"
22
+ autoload :PackageAliasIndex, "ea/model/package_alias_index"
22
23
  autoload :Bounds, "ea/model/bounds"
23
24
  autoload :Waypoint, "ea/model/waypoint"
24
25
  autoload :Annotation, "ea/model/annotation"
@@ -82,18 +82,14 @@ module Ea
82
82
  { literals: literals }
83
83
  end
84
84
 
85
+ # A name that already carries a namespace prefix
86
+ # (e.g. "gml:CodeType") is the qualified name as saved.
85
87
  def qualified_name(element, containing_package)
86
- # The XMI's `name` attribute may already be in
87
- # "ns:Class" form (e.g. "xs:string" lives in the `xs`
88
- # package). Forcing the package name in front would
89
- # produce "xs::xs:string". Strip the existing namespace
90
- # prefix from the simple name before composing the
91
- # qualified name.
92
- raw = element.name.to_s
93
- simple = raw.split(":").last
94
- simple = raw if simple.nil? || simple.empty?
88
+ name = element.name.to_s
89
+ return name if name.include?(":")
90
+
95
91
  pkg_name = containing_package&.name
96
- pkg_name ? "#{pkg_name}::#{simple}" : simple
92
+ pkg_name ? "#{pkg_name}::#{name}" : name
97
93
  end
98
94
 
99
95
  def boolean(value)
@@ -6,9 +6,10 @@ module Ea
6
6
  module Sources
7
7
  module Xmi
8
8
  class DiagramBuilder
9
- # Ordered waypoints of the UMLDI edge EA exported for a connector
10
- # on a diagram. A connector drawn by more than one edge in the same
11
- # diagram, or by an edge with fewer than two waypoints, has no route.
9
+ # The UMLDI edge EA exported for a connector on a diagram: its
10
+ # ordered waypoints and its source/target model elements. A connector
11
+ # drawn by more than one edge in the same diagram has neither; an edge
12
+ # with fewer than two waypoints has no route.
12
13
  class UmldiEdges
13
14
  def initialize(xmi_path)
14
15
  @xmi_path = xmi_path
@@ -18,8 +19,34 @@ module Ea
18
19
  routes.dig(diagram_id, connector_id)
19
20
  end
20
21
 
22
+ def ends_for(diagram_id, connector_id)
23
+ ends.dig(diagram_id, connector_id)
24
+ end
25
+
21
26
  private
22
27
 
28
+ def ends
29
+ @ends ||= doc.xpath("//*[local-name()=\"Diagram\"]").to_h do |diagram|
30
+ [diagram["id"] || diagram["xmi:id"], diagram_ends(diagram)]
31
+ end
32
+ end
33
+
34
+ def diagram_ends(diagram)
35
+ unique_edges(diagram).each_with_object({}) do |(connector_id, edge), acc|
36
+ acc[connector_id] = [edge["source"], edge["target"]] if edge["source"] && edge["target"]
37
+ end
38
+ end
39
+
40
+ # Edges keyed by connector, keeping only connectors drawn by exactly
41
+ # one edge of the diagram, whatever that edge carries.
42
+ def unique_edges(diagram)
43
+ diagram.xpath("./ownedElement[@modelElement]")
44
+ .select { |node| node.attributes["type"]&.value.to_s.end_with?("UMLEdge") }
45
+ .group_by { |edge| edge["modelElement"] }
46
+ .select { |_, same| same.one? }
47
+ .transform_values(&:first)
48
+ end
49
+
23
50
  def routes
24
51
  @routes ||= doc.xpath("//*[local-name()=\"Diagram\"]").to_h do |diagram|
25
52
  [diagram["id"] || diagram["xmi:id"], diagram_routes(diagram)]
@@ -27,9 +54,8 @@ module Ea
27
54
  end
28
55
 
29
56
  def diagram_routes(diagram)
30
- edges = diagram.xpath("./ownedElement[@modelElement][waypoint]").group_by { |edge| edge["modelElement"] }
31
- edges.each_with_object({}) do |(connector_id, same), acc|
32
- points = same.one? ? waypoints(same.first) : []
57
+ unique_edges(diagram).each_with_object({}) do |(connector_id, edge), acc|
58
+ points = waypoints(edge)
33
59
  acc[connector_id] = points if points.size >= 2
34
60
  end
35
61
  end
@@ -52,10 +52,12 @@ module Ea
52
52
  name: props&.name || ext_diagram.id,
53
53
  package_id: ext_diagram.model&.package,
54
54
  diagram_type: props&.type&.split(":")&.last&.downcase,
55
- bounds: canvas_bounds(placed_elements),
55
+ bounds: page_bounds(ext_diagram),
56
56
  style: style_for(ext_diagram),
57
57
  style_ex: style_ex_for(ext_diagram),
58
58
  show_notes: style_ex_for(ext_diagram).to_s.include?("ShowNotes=1"),
59
+ show_package_contents: style_flag?(ext_diagram, "PackageContents"),
60
+ show_foreign: style_flag?(ext_diagram, "HighlightForeign"),
59
61
  elements: build_elements(placed_elements, id, umldi_keywords),
60
62
  connectors: build_connectors(placed_connectors,
61
63
  placed_elements, id),
@@ -89,18 +91,18 @@ module Ea
89
91
 
90
92
  private
91
93
 
92
- def canvas_bounds(placed_elements)
93
- return nil if placed_elements.empty?
94
+ def style_flag?(ext_diagram, key)
95
+ value = style_for(ext_diagram).to_s[/(?:\A|;)#{key}=(\d+)/, 1]
96
+ value.to_i.nonzero? ? true : false
97
+ end
94
98
 
95
- left = placed_elements.map { |e| e.geometry.img_left || e.geometry.left }.compact.min
96
- top = placed_elements.map { |e| e.geometry.img_top || e.geometry.top }.compact.min
97
- right = placed_elements.map { |e| e.geometry.img_right || e.geometry.right }.compact.max
98
- bottom = placed_elements.map { |e| e.geometry.img_bottom || e.geometry.bottom }.compact.max
99
- return nil unless left && top && right && bottom
99
+ def page_bounds(ext_diagram)
100
+ style = style_for(ext_diagram).to_s
101
+ width = style[/(?:\A|;)DocSize\.cx=(\d+)/, 1]
102
+ height = style[/(?:\A|;)DocSize\.cy=(\d+)/, 1]
103
+ return nil unless width && height
100
104
 
101
- Ea::Model::Bounds.new(x: 0, y: 0,
102
- width: right - left,
103
- height: bottom - top)
105
+ Ea::Model::Bounds.new(x: 0, y: 0, width: width.to_i, height: height.to_i)
104
106
  end
105
107
 
106
108
  def build_elements(placed, owner_id, umldi_keywords = {})
@@ -110,12 +112,13 @@ module Ea
110
112
  def build_element(placed, owner_id, umldi_keywords = {})
111
113
  geom = placed.geometry
112
114
  style = placed.style
115
+ bounds = build_bounds(geom.left, geom.top, geom.right, geom.bottom)
113
116
  Ea::Model::DiagramElement.new(
114
117
  id: IdNormalizer.synthetic_id(owner_id, "elem", placed.subject),
115
118
  diagram_id: owner_id,
116
119
  model_element_ref: placed.subject,
117
- bounds: build_bounds(geom.left, geom.top, geom.right, geom.bottom),
118
- image_bounds: build_bounds(geom.img_left, geom.img_top, geom.img_right, geom.img_bottom),
120
+ bounds: bounds,
121
+ image_bounds: (build_bounds(geom.img_left, geom.img_top, geom.img_right, geom.img_bottom) unless bounds),
119
122
  background_color: style.background_color,
120
123
  line_color: style.line_color,
121
124
  line_width: style.line_width,
@@ -146,17 +149,19 @@ module Ea
146
149
  by_duid = placed_elements.each_with_object({}) do |pe, acc|
147
150
  acc[pe.style.duid] = pe if pe.style.duid
148
151
  end
152
+ by_subject = placed_elements.group_by { |pe| guid_key(pe.subject) }
149
153
 
150
154
  placed_connectors.each_with_index.map do |placed, index|
151
- build_connector(placed, by_duid, owner_id, index)
155
+ build_connector(placed, by_duid, by_subject, owner_id, index)
152
156
  end
153
157
  end
154
158
 
155
- def build_connector(placed, by_duid, owner_id, rank)
159
+ def build_connector(placed, by_duid, by_subject, owner_id, rank)
156
160
  geom = placed.geometry
157
161
  style = placed.style
158
- source = by_duid[style.soid]
159
- target = by_duid[style.eoid]
162
+ edge_source, edge_target = umldi_ends(owner_id, placed)
163
+ source = by_duid[style.soid] || sole_placement(by_subject, edge_source)
164
+ target = by_duid[style.eoid] || sole_placement(by_subject, edge_target)
160
165
  ext_meta = connector_meta(placed.subject)
161
166
  route = connector_route(placed, source, target, owner_id, ext_meta[:ea_type])
162
167
  Ea::Model::DiagramConnector.new(
@@ -234,6 +239,23 @@ module Ea
234
239
  route&.map { |x, y| Ea::Model::Waypoint.new(position: Ea::Model::Point.new(x: x, y: y)) }
235
240
  end
236
241
 
242
+ def umldi_ends(diagram_id, placed)
243
+ return unless xmi_path
244
+
245
+ umldi_edges.ends_for(diagram_id, placed.subject)
246
+ end
247
+
248
+ def sole_placement(by_subject, subject)
249
+ placements = by_subject[guid_key(subject)]
250
+ placements.first if placements&.one?
251
+ end
252
+
253
+ # EA spells one GUID with an EAID_ or EAPK_ prefix and mixed case
254
+ # depending on the element kind.
255
+ def guid_key(subject)
256
+ subject&.sub(/\AEA(?:ID|PK)_/, "")&.upcase
257
+ end
258
+
237
259
  def umldi_edges
238
260
  @umldi_edges ||= UmldiEdges.new(xmi_path)
239
261
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Sources
5
+ module Xmi
6
+ # Builds an `Ea::Model::Legend` from the `value` of an element's
7
+ # `<xrefs>` element. The CustomProperties description inside it
8
+ # uses the same `@PROP=...@ENDPROP;` encoding as the QEA t_xref
9
+ # row, so parsing is delegated to the QEA legend builder.
10
+ #
11
+ # Returns nil when the value carries no legend properties.
12
+ class LegendBuilder
13
+ CLIENT = "xmi-element"
14
+
15
+ XrefRow = Struct.new(:name, :description)
16
+
17
+ # Answers the one CustomProperties xref lookup the QEA builder makes.
18
+ class SingleElementXrefs
19
+ def initialize(description)
20
+ @rows = [XrefRow.new(Qea::LegendBuilder::LEGEND_XREF_NAME, description)]
21
+ end
22
+
23
+ def xrefs_for_client(_guid)
24
+ @rows
25
+ end
26
+ end
27
+
28
+ def initialize(xrefs_value)
29
+ @xrefs_value = xrefs_value
30
+ end
31
+
32
+ def build
33
+ return nil if @xrefs_value.to_s.empty?
34
+
35
+ Qea::LegendBuilder.new(SingleElementXrefs.new(@xrefs_value)).build_for(CLIENT)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -10,9 +10,12 @@ module Ea
10
10
  # inside `<xmi:Extension>/<elements>`. Each carries a
11
11
  # `<properties documentation="..."/>` child with the body text.
12
12
  #
13
- # The xmi gem parses Note elements as part of the extension
14
- # block; we walk them and project to Ea::Model::Note.
13
+ # `uml:Text` elements are projected the same way; those whose
14
+ # CustomProperties xref carries legend configuration also
15
+ # get an `Ea::Model::Legend` payload.
15
16
  class NoteBuilder
17
+ NOTE_TYPES = { "uml:Note" => "Note", "uml:Text" => "Text" }.freeze
18
+
16
19
  attr_reader :root
17
20
 
18
21
  def initialize(root)
@@ -29,17 +32,23 @@ module Ea
29
32
  private
30
33
 
31
34
  def build_one(ext_element)
32
- type = ext_element.type
33
- return nil unless type == "uml:Note"
35
+ note_type = NOTE_TYPES[ext_element.type]
36
+ return nil unless note_type
34
37
 
35
- props = ext_element.properties
36
38
  Ea::Model::Note.new(
37
39
  id: ext_element.idref,
38
40
  name: ext_element.name,
39
- body: props&.documentation,
40
- note_type: "Note"
41
+ body: ext_element.properties&.documentation,
42
+ note_type: note_type,
43
+ legend: legend_for(ext_element, note_type)
41
44
  )
42
45
  end
46
+
47
+ def legend_for(ext_element, note_type)
48
+ return nil unless note_type == "Text"
49
+
50
+ LegendBuilder.new(ext_element.xrefs&.value).build
51
+ end
43
52
  end
44
53
  end
45
54
  end
@@ -9,6 +9,8 @@ module Ea
9
9
  class RelationshipBuilder
10
10
  # Connector types that imply an aggregation on the source end
11
11
  # when the end itself records none.
12
+ CONNECTOR_STEREOTYPE = /(?<=@STEREO;Name=)[^;]+/
13
+
12
14
  AGGREGATION_FOR_CONNECTOR_TYPE = {
13
15
  "Aggregation" => "shared",
14
16
  "Composition" => "composite"
@@ -28,7 +30,7 @@ module Ea
28
30
  rel = build_one(element)
29
31
  relationships << rel if rel
30
32
  end
31
- relationships
33
+ relationships + package_import_relationships(relationships)
32
34
  end
33
35
 
34
36
  def build_one(element)
@@ -40,6 +42,30 @@ module Ea
40
42
 
41
43
  private
42
44
 
45
+ def package_import_relationships(built)
46
+ built_ids = built.to_set(&:id)
47
+ connector_index.filter_map do |id, connector|
48
+ next if built_ids.include?(id) || connector.properties&.ea_type != "Package"
49
+
50
+ package_import_relationship(id, connector)
51
+ end
52
+ end
53
+
54
+ def connector_stereotype(connector)
55
+ connector&.xrefs&.value.to_s[CONNECTOR_STEREOTYPE]
56
+ end
57
+
58
+ def package_import_relationship(id, connector)
59
+ Ea::Model::Association.new(
60
+ id: id,
61
+ stereotype: connector_stereotype(connector),
62
+ source_id: connector.source&.idref,
63
+ target_id: connector.target&.idref,
64
+ source_navigable: navigable?(id, :source),
65
+ target_navigable: navigable?(id, :target)
66
+ )
67
+ end
68
+
43
69
  def walk(element, &block)
44
70
  yield element if block
45
71
  element.packaged_element.each { |inner| walk(inner, &block) }
@@ -51,6 +77,7 @@ module Ea
51
77
  id: id,
52
78
  name: element.name,
53
79
  qualified_name: element.name,
80
+ stereotype: connector_stereotype(connector_index[id]),
54
81
  annotations: AnnotationBuilder.from_element(element, id)
55
82
  }
56
83
  end
@@ -88,8 +115,8 @@ module Ea
88
115
  target_multiplicity_raw: raw_card(target.type&.multiplicity),
89
116
  source_aggregation: source_aggregation(connector),
90
117
  target_aggregation: target.type&.aggregation || "none",
91
- source_navigable: navigable?(element, :source),
92
- target_navigable: navigable?(element, :target)
118
+ source_navigable: navigable?(element.id, :source),
119
+ target_navigable: navigable?(element.id, :target)
93
120
  }
94
121
  end
95
122
 
@@ -118,8 +145,8 @@ module Ea
118
145
  target_multiplicity_upper: parse_upper(target_end),
119
146
  source_aggregation: source_end&.aggregation || "none",
120
147
  target_aggregation: target_end&.aggregation || "none",
121
- source_navigable: navigable?(element, :source),
122
- target_navigable: navigable?(element, :target)
148
+ source_navigable: navigable?(element.id, :source),
149
+ target_navigable: navigable?(element.id, :target)
123
150
  }
124
151
  end
125
152
 
@@ -197,8 +224,8 @@ module Ea
197
224
  end
198
225
  end
199
226
 
200
- def navigable?(element, endpoint)
201
- connector = connector_index[element.id]
227
+ def navigable?(id, endpoint)
228
+ connector = connector_index[id]
202
229
  end_element = connector&.public_send(endpoint)
203
230
  flag = end_element&.modifiers&.is_navigable
204
231
  flag.nil? || flag
@@ -25,6 +25,7 @@ module Ea
25
25
  autoload :RelationshipBuilder, "ea/sources/xmi/relationship_builder"
26
26
  autoload :AnnotationBuilder, "ea/sources/xmi/annotation_builder"
27
27
  autoload :TagBuilder, "ea/sources/xmi/tag_builder"
28
+ autoload :LegendBuilder, "ea/sources/xmi/legend_builder"
28
29
  autoload :NoteBuilder, "ea/sources/xmi/note_builder"
29
30
  autoload :StereotypeBuilder, "ea/sources/xmi/stereotype_builder"
30
31
  autoload :DiagramBuilder, "ea/sources/xmi/diagram_builder"
data/lib/ea/version.rb CHANGED
@@ -4,5 +4,5 @@ module Ea
4
4
  # Gem release version. Extracted to its own file so the
5
5
  # `gem-release` tool's `gem bump` can find and update it via the
6
6
  # standard `lib/<gem>/version.rb` convention.
7
- VERSION = "0.6.133"
7
+ VERSION = "0.6.134"
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ea
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.133
4
+ version: 0.6.134
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -680,6 +680,7 @@ files:
680
680
  - lib/ea/model/note.rb
681
681
  - lib/ea/model/operation.rb
682
682
  - lib/ea/model/package.rb
683
+ - lib/ea/model/package_alias_index.rb
683
684
  - lib/ea/model/parameter.rb
684
685
  - lib/ea/model/point.rb
685
686
  - lib/ea/model/polymorphic_maps.rb
@@ -858,6 +859,7 @@ files:
858
859
  - lib/ea/sources/xmi/extension_types.rb
859
860
  - lib/ea/sources/xmi/id_normalizer.rb
860
861
  - lib/ea/sources/xmi/instance_builder.rb
862
+ - lib/ea/sources/xmi/legend_builder.rb
861
863
  - lib/ea/sources/xmi/metadata_builder.rb
862
864
  - lib/ea/sources/xmi/note_builder.rb
863
865
  - lib/ea/sources/xmi/operation_builder.rb