ea 0.5.0 → 0.5.1

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +128 -1
  3. data/TODO.complete/51-remove-respond-to.md +25 -0
  4. data/TODO.complete/52-xsd-decouple-fixtures.md +23 -0
  5. data/TODO.complete/53-xmi-public-send-cleanup.md +26 -0
  6. data/TODO.complete/54-wire-stereotype-icon-renderer.md +20 -0
  7. data/TODO.complete/55-wire-shapescript.md +20 -0
  8. data/TODO.complete/56-wire-emf-renderer.md +27 -0
  9. data/TODO.complete/57-wire-ocl-evaluator.md +23 -0
  10. data/TODO.complete/58-diff-modifications.md +21 -0
  11. data/TODO.complete/59-delete-toy-xmi-export.md +30 -0
  12. data/TODO.complete/60-curate-json-export.md +20 -0
  13. data/TODO.complete/61-plantuml-relationships.md +22 -0
  14. data/TODO.complete/62-changelog.md +17 -0
  15. data/TODO.complete/63-dependabot-fix.md +18 -0
  16. data/TODO.complete/STATUS.md +40 -64
  17. data/lib/ea/cli/command/export.rb +23 -2
  18. data/lib/ea/diff/comparator.rb +28 -1
  19. data/lib/ea/export/json/generator.rb +84 -8
  20. data/lib/ea/export/plantuml/generator.rb +95 -28
  21. data/lib/ea/export/xsd/generator.rb +10 -15
  22. data/lib/ea/export.rb +0 -1
  23. data/lib/ea/lint/rules/missing_stereotype.rb +16 -11
  24. data/lib/ea/ocl/evaluator.rb +2 -2
  25. data/lib/ea/qea/models/ea_implement.rb +2 -2
  26. data/lib/ea/qea/models/ea_object_effort.rb +2 -2
  27. data/lib/ea/qea/models/ea_object_problem.rb +2 -2
  28. data/lib/ea/qea/models/ea_object_require.rb +2 -2
  29. data/lib/ea/qea/models/ea_object_resource.rb +2 -2
  30. data/lib/ea/qea/models/ea_object_risk.rb +2 -2
  31. data/lib/ea/qea/models/ea_object_scenario.rb +2 -2
  32. data/lib/ea/qea/models/ea_object_test.rb +2 -2
  33. data/lib/ea/qea/models/ea_object_trx.rb +2 -2
  34. data/lib/ea/qea/models/ea_palette_item.rb +2 -2
  35. data/lib/ea/qea/models/ea_role_constraint.rb +2 -2
  36. data/lib/ea/qea/models/ea_secrypt.rb +2 -2
  37. data/lib/ea/qea/validation/database/ocl_constraint_validator.rb +65 -0
  38. data/lib/ea/qea/validation/database.rb +2 -0
  39. data/lib/ea/qea/validation/validation_engine.rb +2 -0
  40. data/lib/ea/qea/validation.rb +2 -0
  41. data/lib/ea/query/builder.rb +22 -3
  42. data/lib/ea/svg/ea_emitter/compartment/stereotype_icon.rb +32 -0
  43. data/lib/ea/svg/ea_emitter/compartment.rb +2 -0
  44. data/lib/ea/svg/ea_emitter/document.rb +33 -0
  45. data/lib/ea/svg/ea_emitter/element/stereotype_icon_renderer.rb +49 -12
  46. data/lib/ea/validation/xmi_parity.rb +80 -0
  47. data/lib/ea/validation.rb +12 -0
  48. data/lib/ea/version.rb +1 -1
  49. data/lib/ea.rb +1 -0
  50. metadata +18 -3
  51. data/lib/ea/export/xmi/generator.rb +0 -77
  52. data/lib/ea/export/xmi.rb +0 -11
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Validation
6
+ # Evaluates OCL invariants stored in t_objectconstraint.
7
+ # For each constraint, parse its OCL expression via
8
+ # Ea::Ocl::Parser and evaluate it against the owning
9
+ # object's attribute values. Failures surface as :warning
10
+ # messages — OCL failures aren't structural errors.
11
+ class OclConstraintValidator < BaseValidator
12
+ def validate
13
+ constraints = database.collections[:object_constraints] || []
14
+ constraints.each { |c| check_constraint(c) }
15
+ end
16
+
17
+ private
18
+
19
+ def check_constraint(constraint)
20
+ owner = owner_for(constraint)
21
+ return unless owner
22
+
23
+ ast = parse(constraint.constraint)
24
+ return unless ast
25
+
26
+ if evaluate(ast, owner)
27
+ result.add(:info, "OCL constraint '#{constraint.constraint_type}' " \
28
+ "passed for #{owner.name}",
29
+ entity_name: owner.name)
30
+ else
31
+ result.add(:warning, "OCL constraint '#{constraint.constraint_type}' " \
32
+ "failed for #{owner.name}: #{constraint.constraint}",
33
+ entity_name: owner.name)
34
+ end
35
+ rescue Ea::Ocl::UnsupportedError => e
36
+ result.add(:info, "Skipping OCL constraint on #{owner&.name} " \
37
+ "(unsupported syntax): #{e.message}",
38
+ entity_name: owner&.name)
39
+ end
40
+
41
+ # Parse the OCL body. Returns nil for empty / non-invariant
42
+ # constraints (we don't try to evaluate non-invariant forms).
43
+ def parse(body)
44
+ return nil if body.nil? || body.empty?
45
+
46
+ Ea::Ocl::Parser.parse(body)
47
+ end
48
+
49
+ def evaluate(ast, owner)
50
+ Ea::Ocl::Evaluator.evaluate(ast, owner)
51
+ end
52
+
53
+ def owner_for(constraint)
54
+ object_id = constraint.respond_to?(:ea_object_id) ?
55
+ constraint.ea_object_id :
56
+ constraint.object_id
57
+ return nil unless object_id
58
+
59
+ (database.collections[:objects] || [])
60
+ .find { |o| o.object_id == object_id }
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -10,6 +10,8 @@ module Ea
10
10
  "ea/qea/validation/database/orphan_validator"
11
11
  autoload :ReferentialIntegrityValidator,
12
12
  "ea/qea/validation/database/referential_integrity_validator"
13
+ autoload :OclConstraintValidator,
14
+ "ea/qea/validation/database/ocl_constraint_validator"
13
15
  end
14
16
  end
15
17
  end
@@ -83,6 +83,7 @@ module Ea
83
83
  orphan
84
84
  circular_reference
85
85
  package
86
+ ocl_constraint
86
87
  ]
87
88
 
88
89
  validator_names = if validators
@@ -213,6 +214,7 @@ module Ea
213
214
  @registry.register(:circular_reference,
214
215
  CircularReferenceValidator)
215
216
  @registry.register(:package, PackageValidator)
217
+ @registry.register(:ocl_constraint, OclConstraintValidator)
216
218
 
217
219
  # Phase 2: UML Tree Structure Validators
218
220
  # DocumentStructureValidator lives in lutaml-uml and may not be
@@ -20,6 +20,8 @@ module Ea
20
20
  "ea/qea/validation/database/orphan_validator"
21
21
  autoload :CircularReferenceValidator,
22
22
  "ea/qea/validation/database/circular_reference_validator"
23
+ autoload :OclConstraintValidator,
24
+ "ea/qea/validation/database/ocl_constraint_validator"
23
25
  autoload :Database, "ea/qea/validation/database"
24
26
  autoload :Formatters, "ea/qea/validation/formatters"
25
27
  autoload :ValidationEngine, "ea/qea/validation/validation_engine"
@@ -58,13 +58,15 @@ module Ea
58
58
  filter_by { |o| o.package_id == pkg.package_id }
59
59
  end
60
60
 
61
- # Filter by applied stereotype name.
61
+ # Filter by applied stereotype name. Stereotype lookup walks
62
+ # t_xref for @STEREO blocks referencing the object's ea_guid.
62
63
  # @param name [String]
63
64
  # @return [Builder]
64
65
  def with_stereotype(name)
66
+ xrefs = model.collections[:xrefs] || []
65
67
  filter_by do |o|
66
- refs = o.respond_to?(:stereotype_refs) ? o.stereotype_refs : nil
67
- refs&.any? { |r| r == name }
68
+ applied = stereotype_names_for(o, xrefs)
69
+ applied.any? { |r| r == name }
68
70
  end
69
71
  end
70
72
 
@@ -90,6 +92,23 @@ module Ea
90
92
  Builder.new(model, filters: filters + [block], kind: @kind)
91
93
  end
92
94
 
95
+ # Extract stereotype names applied to an object via t_xref.
96
+ # Returns [] when no @STEREO block references the object's GUID.
97
+ # @param object [Ea::Qea::Models::EaObject]
98
+ # @param xrefs [Array<Ea::Qea::Models::EaXref>]
99
+ # @return [Array<String>]
100
+ def stereotype_names_for(object, xrefs)
101
+ return [] unless object.is_a?(Ea::Qea::Models::EaObject)
102
+ return [] unless object.ea_guid
103
+
104
+ xrefs.select do |xr|
105
+ xr.client == object.ea_guid && xr.description&.include?("@STEREO")
106
+ end.map do |xr|
107
+ xr.description.match(/Name=([^;]+)/)[1]
108
+ end.compact
109
+ end
110
+ private :stereotype_names_for
111
+
93
112
  # Materialize the filtered collection.
94
113
  # @return [Array]
95
114
  def call
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Stereotype decorator icon compartment. Emits the small
8
+ # symbolic polygon that EA renders inside an element's body
9
+ # when a stereotype provides a custom icon (FeatureType
10
+ # diamond, Type polygon, etc.). Delegates to
11
+ # Element::StereotypeIconRenderer.
12
+ #
13
+ # Rendered after the header so the icon sits inside the
14
+ # element body below the stereotype label.
15
+ module StereotypeIcon
16
+ module_function
17
+
18
+ def render(context)
19
+ return nil unless context.classifier
20
+
21
+ svg = Element::StereotypeIconRenderer.render(
22
+ classifier: context.classifier,
23
+ bounds: context.bounds,
24
+ canvas: context.canvas
25
+ )
26
+ svg.empty? ? nil : svg
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -14,6 +14,7 @@ module Ea
14
14
  autoload :NoteBody, "ea/svg/ea_emitter/compartment/note_body"
15
15
  autoload :Header, "ea/svg/ea_emitter/compartment/header"
16
16
  autoload :HeaderDivider, "ea/svg/ea_emitter/compartment/header_divider"
17
+ autoload :StereotypeIcon, "ea/svg/ea_emitter/compartment/stereotype_icon"
17
18
  autoload :Attributes, "ea/svg/ea_emitter/compartment/attributes"
18
19
  autoload :Operations, "ea/svg/ea_emitter/compartment/operations"
19
20
  autoload :EnumLiterals, "ea/svg/ea_emitter/compartment/enum_literals"
@@ -32,6 +33,7 @@ module Ea
32
33
  NoteBody,
33
34
  Header,
34
35
  HeaderDivider,
36
+ StereotypeIcon,
35
37
  Attributes,
36
38
  Operations,
37
39
  EnumLiterals,
@@ -26,9 +26,42 @@ module Ea
26
26
  document: document)
27
27
  .layers
28
28
  .reject { |s| s.nil? || s.empty? }
29
+ image_layer = emit_images
30
+ layers << image_layer if image_layer
29
31
 
30
32
  %(<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">\n\n<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="#{canvas.width_cm}" height="#{canvas.height_cm}" viewBox="#{canvas.view_box}">\n<title></title>\n<desc>Created with Enterprise Architect (Build: #{BUILD_ID}) 2</desc>\n#{layers.join("\n")}\n</svg>)
31
33
  end
34
+
35
+ # Walks t_image rows (when the document carries a Database
36
+ # with an :images collection) and invokes EmfRenderer on
37
+ # each. Emits an `<image>` element per successfully converted
38
+ # SVG, or returns nil when no images or all conversions fail.
39
+ #
40
+ # The emfsvg gem currently can't parse EA's specific EMF
41
+ # variant (see TODO.diagrams/87 + TODO.complete/56). Until
42
+ # upstream emfsvg supports it, this method returns nil
43
+ # gracefully. The wiring is correct infrastructure for when
44
+ # emfsvg catches up.
45
+ def emit_images
46
+ return nil unless document
47
+ return nil unless document.is_a?(Ea::Qea::Database)
48
+
49
+ images = document.collections[:images] || []
50
+ return nil if images.empty?
51
+
52
+ fragments = images.filter_map { |image| render_image(image) }
53
+ return nil if fragments.empty?
54
+
55
+ %(<g id="images">\n#{fragments.join("\n")}\n</g>)
56
+ end
57
+
58
+ def render_image(image)
59
+ svg = Ea::Image::EmfRenderer.render(image.bytes)
60
+ return nil unless svg
61
+
62
+ %(<g>#{svg}</g>)
63
+ end
64
+ private :render_image
32
65
  end
33
66
  end
34
67
  end
@@ -28,22 +28,63 @@ module Ea
28
28
  OFFSET_X = 0
29
29
  OFFSET_Y = 0
30
30
 
31
- attr_reader :classifier, :bounds, :canvas
31
+ attr_reader :classifier, :bounds, :canvas, :mdg_registry
32
32
 
33
- def initialize(classifier:, bounds:, canvas: nil)
33
+ # @param mdg_registry [Ea::Mdg::Registry, nil] optional
34
+ # registry to look up MDG-provided ShapeScript for the
35
+ # classifier's stereotype. When provided and the matching
36
+ # stereotype has a ShapeScript body, it's parsed and
37
+ # rendered. Otherwise falls back to FALLBACK_ICONS.
38
+ def initialize(classifier:, bounds:, canvas: nil, mdg_registry: nil)
34
39
  @classifier = classifier
35
40
  @bounds = bounds
36
41
  @canvas = canvas
42
+ @mdg_registry = mdg_registry
37
43
  end
38
44
 
39
- def self.render(classifier:, bounds:, canvas: nil, **_)
40
- new(classifier: classifier, bounds: bounds, canvas: canvas).to_svg
45
+ def self.render(classifier:, bounds:, canvas: nil, **opts)
46
+ new(classifier: classifier, bounds: bounds, canvas: canvas, **opts).to_svg
41
47
  end
42
48
 
43
- # @return [String] SVG polygon fragment, or "" if no icon applies
49
+ # @return [String] SVG fragment, or "" if no icon applies
44
50
  def to_svg
45
51
  return "" unless classifier && bounds
52
+ return "" unless stereotype_name
46
53
 
54
+ shapescript_svg || fallback_svg
55
+ end
56
+
57
+ private
58
+
59
+ # Try MDG-provided ShapeScript first.
60
+ # @return [String, nil] SVG fragment from parsed ShapeScript
61
+ def shapescript_svg
62
+ return nil unless mdg_registry
63
+
64
+ body = lookup_shapescript(stereotype_name)
65
+ return nil unless body
66
+
67
+ shapes = Ea::Shapescript::Parser.parse(body)
68
+ return nil if shapes.empty?
69
+
70
+ Ea::Shapescript::Renderer.render(shapes, fill: "#FAF1EC",
71
+ stroke: "#69738C")
72
+ end
73
+
74
+ # @return [String, nil] ShapeScript source for the stereotype, if any
75
+ def lookup_shapescript(name)
76
+ mdg_registry.documents.each do |doc|
77
+ stereo = (doc.stereotypes || []).find { |s| s.name == name }
78
+ next unless stereo
79
+
80
+ notes = stereo.notes
81
+ return notes if notes && notes.include?("shape")
82
+ end
83
+ nil
84
+ end
85
+
86
+ # @return [String] hardcoded fallback polygon, or "" if none
87
+ def fallback_svg
47
88
  spec = FALLBACK_ICONS[stereotype_name]
48
89
  return "" unless spec
49
90
 
@@ -55,15 +96,11 @@ module Ea
55
96
  %(<polygon points="#{pts_str}" fill="#{fill}" stroke="#{stroke}" stroke-width="1"/>)
56
97
  end
57
98
 
58
- private
59
-
60
99
  def stereotype_name
61
- refs = classifier.respond_to?(:stereotype_refs) ? classifier.stereotype_refs : nil
62
- return refs.first if refs&.any?
100
+ return nil unless classifier.is_a?(Ea::Model::Classifier)
63
101
 
64
- # Walk xref description as a fallback when classifier has no
65
- # explicit stereotype_refs slot (e.g. raw Ea::Qea::Models::EaObject).
66
- nil
102
+ refs = classifier.stereotype_refs
103
+ refs&.first
67
104
  end
68
105
 
69
106
  def translate_point(x, y)
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Validation
5
+ # Compares Ea::Transformers.qea_to_xmi output against EA's
6
+ # reference XMI export (`examples/exports/*/model.xml`).
7
+ #
8
+ # Reports per-element-type counts (packagedElement, ownedAttribute,
9
+ # ownedEnd, memberEnd, generalization, connector, diagram, style,
10
+ # tags, documentation, xmi:Extension) so gaps surface clearly.
11
+ #
12
+ # Used by the parity regression spec to track XMI export fidelity
13
+ # over time. Improvements to QeaToXmi should shrink the gaps.
14
+ class XmiParity
15
+ ELEMENT_TYPES = %w[
16
+ packagedElement
17
+ ownedAttribute
18
+ ownedOperation
19
+ ownedEnd
20
+ memberEnd
21
+ generalization
22
+ connector
23
+ diagram
24
+ taggedValue
25
+ xmi:Extension
26
+ style
27
+ tags
28
+ documentation
29
+ ].freeze
30
+
31
+ # Per-element counts for one XMI document.
32
+ Counts = Struct.new(:total, :by_type, keyword_init: true) do
33
+ def initialize(*)
34
+ super
35
+ self.by_type ||= {}
36
+ end
37
+
38
+ def delta(other)
39
+ result = { total: total - other.total }
40
+ ELEMENT_TYPES.each do |t|
41
+ result[t] = (by_type[t] || 0) - (other.by_type[t] || 0)
42
+ end
43
+ result
44
+ end
45
+ end
46
+
47
+ # @param qea_path [String] path to the QEA file
48
+ # @param reference_xmi_path [String] path to EA's reference XMI
49
+ # @return [Hash] {:ours, :reference, :delta} each a Counts
50
+ def self.compare(qea_path, reference_xmi_path)
51
+ database = Ea::Qea.load(qea_path)
52
+ ours_xml = Ea::Transformers.qea_to_xmi(database)
53
+ ref_xml = File.read(reference_xmi_path)
54
+
55
+ {
56
+ qea: qea_path,
57
+ reference: reference_xmi_path,
58
+ ours: count(ours_xml),
59
+ reference_counts: count(ref_xml),
60
+ delta: count(ours_xml).delta(count(ref_xml))
61
+ }
62
+ end
63
+
64
+ # @param xml [String] XMI markup (any encoding)
65
+ # @return [Counts]
66
+ def self.count(xml)
67
+ # EA exports use windows-1252; force UTF-8 for safe regex.
68
+ safe = xml.to_s.encode("UTF-8", invalid: :replace, undef: :replace,
69
+ replace: "?")
70
+ by_type = ELEMENT_TYPES.each_with_object({}) do |tag, acc|
71
+ # Match `<tag>` followed by space, `/`, or `>` (covers
72
+ # `<tag/>`, `<tag foo=...>`, `<tag>`). Using %r{} to avoid
73
+ # `/` regex-delimiter conflict with the `/` in the class.
74
+ acc[tag] = safe.scan(%r{<#{tag}[\s/>]}).size
75
+ end
76
+ Counts.new(total: by_type.values.sum, by_type: by_type)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string: true
2
+
3
+ module Ea
4
+ # Top-level validation namespace. Hosts cross-cutting validators
5
+ # that span multiple subsystems (QEA + XMI, lint rules, OCL).
6
+ #
7
+ # Distinct from Ea::Qea::Validation which is specific to QEA
8
+ # database + UML document structural validation.
9
+ module Validation
10
+ autoload :XmiParity, "ea/validation/xmi_parity"
11
+ end
12
+ end
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.5.0"
7
+ VERSION = "0.5.1"
8
8
  end
data/lib/ea.rb CHANGED
@@ -29,6 +29,7 @@ module Ea
29
29
  autoload :Lint, "ea/lint"
30
30
  autoload :Query, "ea/query"
31
31
  autoload :Ocl, "ea/ocl"
32
+ autoload :Validation, "ea/validation"
32
33
 
33
34
  class << self
34
35
  # Parse an EA file into its native representation.
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.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -223,6 +223,19 @@ files:
223
223
  - TODO.complete/45-shapescript-extensions.md
224
224
  - TODO.complete/46-xsd-consolidation.md
225
225
  - TODO.complete/47-headerlines-legacy-removal.md
226
+ - TODO.complete/51-remove-respond-to.md
227
+ - TODO.complete/52-xsd-decouple-fixtures.md
228
+ - TODO.complete/53-xmi-public-send-cleanup.md
229
+ - TODO.complete/54-wire-stereotype-icon-renderer.md
230
+ - TODO.complete/55-wire-shapescript.md
231
+ - TODO.complete/56-wire-emf-renderer.md
232
+ - TODO.complete/57-wire-ocl-evaluator.md
233
+ - TODO.complete/58-diff-modifications.md
234
+ - TODO.complete/59-delete-toy-xmi-export.md
235
+ - TODO.complete/60-curate-json-export.md
236
+ - TODO.complete/61-plantuml-relationships.md
237
+ - TODO.complete/62-changelog.md
238
+ - TODO.complete/63-dependabot-fix.md
226
239
  - TODO.complete/STATUS.md
227
240
  - TODO.diagrams/01-svg-measurement-harness.md
228
241
  - TODO.diagrams/01-xmi-extension-elements-parsing.md
@@ -586,8 +599,6 @@ files:
586
599
  - lib/ea/export/json_schema/generator.rb
587
600
  - lib/ea/export/plantuml.rb
588
601
  - lib/ea/export/plantuml/generator.rb
589
- - lib/ea/export/xmi.rb
590
- - lib/ea/export/xmi/generator.rb
591
602
  - lib/ea/export/xsd.rb
592
603
  - lib/ea/export/xsd/class_mapping.rb
593
604
  - lib/ea/export/xsd/generator.rb
@@ -753,6 +764,7 @@ files:
753
764
  - lib/ea/qea/validation/class_validator.rb
754
765
  - lib/ea/qea/validation/database.rb
755
766
  - lib/ea/qea/validation/database/circular_reference_validator.rb
767
+ - lib/ea/qea/validation/database/ocl_constraint_validator.rb
756
768
  - lib/ea/qea/validation/database/orphan_validator.rb
757
769
  - lib/ea/qea/validation/database/referential_integrity_validator.rb
758
770
  - lib/ea/qea/validation/diagram_validator.rb
@@ -862,6 +874,7 @@ files:
862
874
  - lib/ea/svg/ea_emitter/compartment/package_contents.rb
863
875
  - lib/ea/svg/ea_emitter/compartment/package_from_parent.rb
864
876
  - lib/ea/svg/ea_emitter/compartment/shape.rb
877
+ - lib/ea/svg/ea_emitter/compartment/stereotype_icon.rb
865
878
  - lib/ea/svg/ea_emitter/compartment/tagged_values.rb
866
879
  - lib/ea/svg/ea_emitter/connectors.rb
867
880
  - lib/ea/svg/ea_emitter/diagram_frame.rb
@@ -940,6 +953,8 @@ files:
940
953
  - lib/ea/transformers/uml_to_xmi/id_generator.rb
941
954
  - lib/ea/transformers/uml_to_xmi/transformer.rb
942
955
  - lib/ea/transformers/uml_to_xmi/writer.rb
956
+ - lib/ea/validation.rb
957
+ - lib/ea/validation/xmi_parity.rb
943
958
  - lib/ea/version.rb
944
959
  - lib/ea/xmi.rb
945
960
  - lib/ea/xmi/liquid_drops/association_drop.rb
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "nokogiri"
4
-
5
- module Ea
6
- module Export
7
- module Xmi
8
- # Generates Sparx-flavored XMI 2.1 from a parsed QEA model.
9
- #
10
- # Uses the existing QEA→XMI transformer under Ea::Transformers
11
- # when available. Falls back to a minimal hand-rolled writer
12
- # that produces a valid XMI root + class list when the bridge
13
- # is unavailable.
14
- class Generator
15
- def self.call(model, **_opts)
16
- new(model).call
17
- end
18
-
19
- attr_reader :model
20
-
21
- # @param model [#collections] Ea::Qea::Database or compatible
22
- def initialize(model)
23
- @model = model
24
- end
25
-
26
- def call
27
- builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
28
- xml.xmi("XMI" => "2.1",
29
- "xmlns:UML" => "http://schema.omg.org/spec/UML/2.1",
30
- "xmlns:xmi" => "http://schema.omg.org/spec/XMI/2.1") do
31
- xml.documentation(:exporter => "ea-rb",
32
- :exporterVersion => Ea::VERSION)
33
- emit_model(xml)
34
- end
35
- end
36
- builder.to_xml
37
- end
38
-
39
- private
40
-
41
- def emit_model(xml)
42
- classes = (model.collections[:objects] || [])
43
- .select { |o| o.object_type == "Class" }
44
-
45
- xml.tag!("uml:Model", "xmi:id" => "model-root",
46
- "name" => "model") do
47
- classes.each do |klass|
48
- emit_class(xml, klass)
49
- end
50
- end
51
- end
52
-
53
- def emit_class(xml, klass)
54
- attrs = class_attributes(klass)
55
- xml.tag!("ownedMember",
56
- "xmi:type" => "uml:Class",
57
- "xmi:id" => klass.ea_guid || klass.object_id.to_s,
58
- "name" => klass.name || "Anonymous") do
59
- attrs.each { |attr| emit_attribute(xml, attr) }
60
- end
61
- end
62
-
63
- def emit_attribute(xml, attr)
64
- xml.tag!("ownedAttribute",
65
- "xmi:type" => "uml:Property",
66
- "xmi:id" => attr.ea_guid || attr.property_id.to_s,
67
- "name" => attr.name)
68
- end
69
-
70
- def class_attributes(klass)
71
- attrs = model.collections[:attributes] || []
72
- attrs.select { |a| a.object_id == klass.object_id }
73
- end
74
- end
75
- end
76
- end
77
- end
data/lib/ea/export/xmi.rb DELETED
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ea
4
- module Export
5
- # XMI export namespace. Generates Sparx-flavored XMI from a
6
- # parsed model.
7
- module Xmi
8
- autoload :Generator, "ea/export/xmi/generator"
9
- end
10
- end
11
- end