lutaml 0.7.7 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +7 -6
  3. data/.rubocop.yml +1 -0
  4. data/LUTAML.adoc +372 -0
  5. data/Makefile +2 -0
  6. data/bin/console +5 -0
  7. data/bin/folder_yaml2lutaml.sh +6 -0
  8. data/bin/plantuml2lutaml +59 -0
  9. data/bin/yaml2lutaml +144 -0
  10. data/exe/lutaml-sysml +20 -0
  11. data/exe/lutaml-wsd2uml +59 -0
  12. data/exe/lutaml-yaml2uml +144 -0
  13. data/lib/lutaml/express/README.adoc +55 -0
  14. data/lib/lutaml/express/parsers/exp.rb +21 -0
  15. data/lib/lutaml/express/version.rb +7 -0
  16. data/lib/lutaml/express.rb +9 -0
  17. data/lib/lutaml/parser.rb +7 -0
  18. data/lib/lutaml/sysml/README.md +40 -0
  19. data/lib/lutaml/sysml/allocate.rb +8 -0
  20. data/lib/lutaml/sysml/allocated.rb +7 -0
  21. data/lib/lutaml/sysml/binding_connector.rb +7 -0
  22. data/lib/lutaml/sysml/block.rb +27 -0
  23. data/lib/lutaml/sysml/constraint_block.rb +12 -0
  24. data/lib/lutaml/sysml/copy.rb +6 -0
  25. data/lib/lutaml/sysml/derive_requirement.rb +7 -0
  26. data/lib/lutaml/sysml/nested_connector_end.rb +11 -0
  27. data/lib/lutaml/sysml/refine.rb +7 -0
  28. data/lib/lutaml/sysml/requirement.rb +34 -0
  29. data/lib/lutaml/sysml/requirement_related.rb +7 -0
  30. data/lib/lutaml/sysml/satisfy.rb +7 -0
  31. data/lib/lutaml/sysml/test_case.rb +22 -0
  32. data/lib/lutaml/sysml/trace.rb +7 -0
  33. data/lib/lutaml/sysml/verify.rb +6 -0
  34. data/lib/lutaml/sysml/version.rb +5 -0
  35. data/lib/lutaml/sysml/xmi_file.rb +417 -0
  36. data/lib/lutaml/sysml.rb +10 -0
  37. data/lib/lutaml/uml/README.adoc +44 -0
  38. data/lib/lutaml/uml/abstraction.rb +11 -0
  39. data/lib/lutaml/uml/activity.rb +11 -0
  40. data/lib/lutaml/uml/actor.rb +19 -0
  41. data/lib/lutaml/uml/association.rb +43 -0
  42. data/lib/lutaml/uml/behavior.rb +11 -0
  43. data/lib/lutaml/uml/class.rb +83 -0
  44. data/lib/lutaml/uml/classifier.rb +11 -0
  45. data/lib/lutaml/uml/connector.rb +21 -0
  46. data/lib/lutaml/uml/constraint.rb +12 -0
  47. data/lib/lutaml/uml/constructor_end.rb +16 -0
  48. data/lib/lutaml/uml/data_type.rb +75 -0
  49. data/lib/lutaml/uml/dependency.rb +21 -0
  50. data/lib/lutaml/uml/diagram.rb +8 -0
  51. data/lib/lutaml/uml/document.rb +81 -0
  52. data/lib/lutaml/uml/enum.rb +45 -0
  53. data/lib/lutaml/uml/event.rb +12 -0
  54. data/lib/lutaml/uml/final_state.rb +11 -0
  55. data/lib/lutaml/uml/formatter/base.rb +67 -0
  56. data/lib/lutaml/uml/formatter/graphviz.rb +334 -0
  57. data/lib/lutaml/uml/formatter.rb +21 -0
  58. data/lib/lutaml/uml/has_attributes.rb +14 -0
  59. data/lib/lutaml/uml/has_members.rb +30 -0
  60. data/lib/lutaml/uml/instance.rb +17 -0
  61. data/lib/lutaml/uml/model.rb +13 -0
  62. data/lib/lutaml/uml/node/base.rb +21 -0
  63. data/lib/lutaml/uml/node/class_node.rb +57 -0
  64. data/lib/lutaml/uml/node/class_relationship.rb +14 -0
  65. data/lib/lutaml/uml/node/document.rb +18 -0
  66. data/lib/lutaml/uml/node/field.rb +34 -0
  67. data/lib/lutaml/uml/node/has_name.rb +15 -0
  68. data/lib/lutaml/uml/node/has_type.rb +15 -0
  69. data/lib/lutaml/uml/node/method.rb +29 -0
  70. data/lib/lutaml/uml/node/method_argument.rb +16 -0
  71. data/lib/lutaml/uml/node/relationship.rb +28 -0
  72. data/lib/lutaml/uml/opaque_behavior.rb +11 -0
  73. data/lib/lutaml/uml/operation.rb +31 -0
  74. data/lib/lutaml/uml/package.rb +53 -0
  75. data/lib/lutaml/uml/parsers/attribute.rb +70 -0
  76. data/lib/lutaml/uml/parsers/dsl.rb +413 -0
  77. data/lib/lutaml/uml/parsers/dsl_preprocessor.rb +59 -0
  78. data/lib/lutaml/uml/parsers/dsl_transform.rb +27 -0
  79. data/lib/lutaml/uml/parsers/yaml.rb +46 -0
  80. data/lib/lutaml/uml/port.rb +8 -0
  81. data/lib/lutaml/uml/primitive_type.rb +14 -0
  82. data/lib/lutaml/uml/property.rb +27 -0
  83. data/lib/lutaml/uml/pseudostate.rb +11 -0
  84. data/lib/lutaml/uml/realization.rb +11 -0
  85. data/lib/lutaml/uml/region.rb +12 -0
  86. data/lib/lutaml/uml/serializers/association.rb +58 -0
  87. data/lib/lutaml/uml/serializers/base.rb +16 -0
  88. data/lib/lutaml/uml/serializers/class.rb +29 -0
  89. data/lib/lutaml/uml/serializers/top_element_attribute.rb +14 -0
  90. data/lib/lutaml/uml/serializers/yaml_view.rb +18 -0
  91. data/lib/lutaml/uml/state.rb +12 -0
  92. data/lib/lutaml/uml/state_machine.rb +12 -0
  93. data/lib/lutaml/uml/top_element.rb +58 -0
  94. data/lib/lutaml/uml/top_element_attribute.rb +39 -0
  95. data/lib/lutaml/uml/transition.rb +12 -0
  96. data/lib/lutaml/uml/trigger.rb +12 -0
  97. data/lib/lutaml/uml/value.rb +31 -0
  98. data/lib/lutaml/uml/version.rb +7 -0
  99. data/lib/lutaml/uml/vertex.rb +11 -0
  100. data/lib/lutaml/uml.rb +13 -0
  101. data/lib/lutaml/version.rb +1 -1
  102. data/lib/lutaml/xmi/README.adoc +24 -0
  103. data/lib/lutaml/xmi/parsers/xml.rb +600 -0
  104. data/lib/lutaml/xmi/version.rb +5 -0
  105. data/lib/lutaml/xmi.rb +7 -0
  106. data/lib/lutaml/xml/lutaml_path/document_wrapper.rb +45 -0
  107. data/lib/lutaml/xml/mapper.rb +448 -0
  108. data/lib/lutaml/xml/parsers/xml.rb +57 -0
  109. data/lib/lutaml/xml.rb +9 -0
  110. data/lutaml.gemspec +8 -3
  111. metadata +192 -16
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ ## Behaviour metamodel
5
+ ##
6
+ module Lutaml
7
+ module Uml
8
+ class Constraint < TopElement
9
+ attr_accessor :body
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ ## Behaviour metamodel
5
+ ##
6
+ module Lutaml
7
+ module Uml
8
+ class ConnectorEnd < TopElement
9
+ attr_accessor :role, :part_with_port, :connector
10
+
11
+ def initialize
12
+ @role = nil
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+ require "lutaml/uml/classifier"
3
+
4
+ module Lutaml
5
+ module Uml
6
+ class DataType < Classifier
7
+ include HasMembers
8
+
9
+ attr_accessor :nested_classifier,
10
+ :is_abstract,
11
+ :type
12
+
13
+ attr_reader :associations,
14
+ :attributes,
15
+ :members,
16
+ :modifier,
17
+ :constraints,
18
+ :operations,
19
+ :data_types
20
+
21
+ def initialize(attributes = {})
22
+ @nested_classifier = []
23
+ @stereotype = []
24
+ @generalization = []
25
+ @is_abstract = false
26
+ super
27
+ @keyword = "dataType"
28
+ end
29
+
30
+ def modifier=(value)
31
+ @modifier = value.to_s # TODO: Validate?
32
+ end
33
+
34
+ def attributes=(value)
35
+ @attributes = value.to_a.map do |attr|
36
+ TopElementAttribute.new(attr)
37
+ end
38
+ end
39
+
40
+ def associations=(value)
41
+ @associations = value.to_a.map do |attr|
42
+ Association.new(attr.to_h.merge(owner_end: name))
43
+ end
44
+ end
45
+
46
+ def constraints=(value)
47
+ @constraints = value.to_a.map do |attr|
48
+ Constraint.new(attr)
49
+ end
50
+ end
51
+
52
+ def operations=(value)
53
+ @operations = value.to_a.map do |attr|
54
+ Operation.new(attr)
55
+ end
56
+ end
57
+
58
+ def data_types=(value)
59
+ @data_types = value.to_a.map do |attr|
60
+ DataType.new(attr)
61
+ end
62
+ end
63
+
64
+ def methods
65
+ # @members&.select { |member| member.class == Method }
66
+ []
67
+ end
68
+
69
+ def relationships
70
+ # @members&.select { |member| member.class == ClassRelationship }
71
+ []
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ ## Behaviour metamodel
5
+ ##
6
+ module Lutaml
7
+ module Uml
8
+ class Dependency < TopElement
9
+ attr_accessor :client, :supplier
10
+
11
+ def initialize
12
+ @name = nil
13
+ @xmi_id = nil
14
+ @xmi_uuid = nil
15
+ @client = []
16
+ @supplier = []
17
+ @namespace = nil
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Uml
5
+ class Diagram < TopElement
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/uml/class"
4
+ require "lutaml/uml/data_type"
5
+ require "lutaml/uml/enum"
6
+ require "lutaml/uml/diagram"
7
+ require "lutaml/uml/package"
8
+ require "lutaml/uml/primitive_type"
9
+
10
+ module Lutaml
11
+ module Uml
12
+ class Document
13
+ include HasAttributes
14
+ include HasMembers
15
+
16
+ attr_accessor :name,
17
+ :title,
18
+ :caption,
19
+ :groups,
20
+ :fidelity,
21
+ :fontname,
22
+ :comments
23
+ attr_reader :packages
24
+
25
+ # rubocop:disable Rails/ActiveRecordAliases
26
+ def initialize(attributes = {})
27
+ update_attributes(attributes)
28
+ end
29
+ # rubocop:enable Rails/ActiveRecordAliases
30
+ def classes=(value)
31
+ @classes = value.to_a.map { |attributes| Class.new(attributes) }
32
+ end
33
+
34
+ def data_types=(value)
35
+ @data_types = value.to_a.map { |attributes| DataType.new(attributes) }
36
+ end
37
+
38
+ def enums=(value)
39
+ @enums = value.to_a.map { |attributes| Enum.new(attributes) }
40
+ end
41
+
42
+ def packages=(value)
43
+ @packages = value.to_a.map { |attributes| Package.new(attributes) }
44
+ end
45
+
46
+ def primitives=(value)
47
+ @primitives = value.to_a.map { |attributes| PrimitiveType.new(attributes) }
48
+ end
49
+
50
+ def associations=(value)
51
+ @associations = value.to_a.map do |attributes|
52
+ Association.new(attributes)
53
+ end
54
+ end
55
+
56
+ def classes
57
+ @classes || []
58
+ end
59
+
60
+ def enums
61
+ @enums || []
62
+ end
63
+
64
+ def data_types
65
+ @data_types || []
66
+ end
67
+
68
+ def packages
69
+ @packages || []
70
+ end
71
+
72
+ def primitives
73
+ @primitives || []
74
+ end
75
+
76
+ def associations
77
+ @associations || []
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/uml/has_members"
4
+ require "lutaml/uml/classifier"
5
+ require "lutaml/uml/association"
6
+ require "lutaml/uml/top_element_attribute"
7
+ require "lutaml/uml/value"
8
+
9
+ module Lutaml
10
+ module Uml
11
+ class Enum < Classifier
12
+ include HasMembers
13
+
14
+ attr_reader :attributes,
15
+ :members,
16
+ :modifier,
17
+ :definition,
18
+ :keyword,
19
+ :values
20
+
21
+ def initialize(attributes = {})
22
+ super
23
+ @keyword = "enumeration"
24
+ end
25
+
26
+ # TODO: delete?
27
+ def attributes=(value)
28
+ @attributes = value.to_a.map do |attr|
29
+ TopElementAttribute.new(attr)
30
+ end
31
+ end
32
+
33
+ def values=(value)
34
+ @values = value.to_a.map do |attr|
35
+ Value.new(attr)
36
+ end
37
+ end
38
+
39
+ # TODO: reserved name, change
40
+ def methods
41
+ []
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ ## Behaviour metamodel
5
+ ##
6
+
7
+ module Lutaml
8
+ module Uml
9
+ class Event < TopElement
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ ## Behaviour metamodel
5
+ ##
6
+ module Lutaml
7
+ module Uml
8
+ class FinalState < State
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/uml/formatter"
4
+ require "lutaml/uml/has_attributes"
5
+
6
+ module Lutaml
7
+ module Uml
8
+ module Formatter
9
+ class Base
10
+ class << self
11
+ def inherited(subclass)
12
+ Formatter.all << subclass
13
+ end
14
+
15
+ def format(node, attributes = {})
16
+ new(attributes).format(node)
17
+ end
18
+
19
+ def name
20
+ to_s.split("::").last.downcase.to_sym
21
+ end
22
+ end
23
+
24
+ include HasAttributes
25
+
26
+ # rubocop:disable Rails/ActiveRecordAliases
27
+ def initialize(attributes = {})
28
+ update_attributes(attributes)
29
+ end
30
+ # rubocop:enable Rails/ActiveRecordAliases
31
+
32
+ def name
33
+ self.class.name
34
+ end
35
+
36
+ attr_reader :type
37
+
38
+ def type=(value)
39
+ @type = value.to_s.strip.downcase.to_sym
40
+ end
41
+
42
+ def format(node)
43
+ case node
44
+ when Node::Field then format_field(node)
45
+ when Node::Method then format_method(node)
46
+ when Node::Relationship then format_relationship(node)
47
+ when Node::ClassRelationship then format_class_relationship(node)
48
+ when Node::ClassNode then format_class(node)
49
+ when Lutaml::Uml::Document then format_document(node)
50
+ end
51
+ end
52
+
53
+ def format_field(_node); raise NotImplementedError; end
54
+
55
+ def format_method(_node); raise NotImplementedError; end
56
+
57
+ def format_relationship(_node); raise NotImplementedError; end
58
+
59
+ def format_class_relationship(_node); raise NotImplementedError; end
60
+
61
+ def format_class(_node); raise NotImplementedError; end
62
+
63
+ def format_document(_node); raise NotImplementedError; end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,334 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "lutaml/uml/formatter/base"
5
+ require "lutaml/layout/graph_viz_engine"
6
+
7
+ module Lutaml
8
+ module Uml
9
+ module Formatter
10
+ class Graphviz < Base
11
+ class Attributes < Hash
12
+ def to_s
13
+ to_a
14
+ .reject { |(_k, val)| val.nil? }
15
+ .map { |(a, b)| "#{a}=#{b.inspect}" }
16
+ .join(" ")
17
+ end
18
+ end
19
+
20
+ ACCESS_SYMBOLS = {
21
+ "public" => "+",
22
+ "protected" => "#",
23
+ "private" => "-",
24
+ }.freeze
25
+ DEFAULT_CLASS_FONT = "Helvetica".freeze
26
+
27
+ VALID_TYPES = %i[
28
+ dot
29
+ xdot
30
+ ps
31
+ pdf
32
+ svg
33
+ svgz
34
+ fig
35
+ png
36
+ gif
37
+ jpg
38
+ jpeg
39
+ json
40
+ imap
41
+ cmapx
42
+ ].freeze
43
+
44
+ def initialize(attributes = {})
45
+ super
46
+
47
+ @graph = Attributes.new
48
+ # Associations lines style, `true` gives curved lines
49
+ # https://graphviz.org/doc/info/attrs.html#d:splines
50
+ @graph["splines"] = "ortho"
51
+ # Padding between outside of picture and nodes
52
+ @graph["pad"] = 0.5
53
+ # Padding between levels
54
+ @graph["ranksep"] = "1.2.equally"
55
+ # Padding between nodes
56
+ @graph["nodesep"] = "1.2.equally"
57
+ # TODO: set rankdir
58
+ # @graph['rankdir'] = 'BT'
59
+
60
+ @edge = Attributes.new
61
+ @edge["color"] = "gray50"
62
+
63
+ @node = Attributes.new
64
+ @node["shape"] = "box"
65
+
66
+ @type = :dot
67
+ end
68
+
69
+ attr_reader :graph
70
+ attr_reader :edge
71
+ attr_reader :node
72
+
73
+ def type=(value)
74
+ super
75
+
76
+ @type = :dot unless VALID_TYPES.include?(@type)
77
+ end
78
+
79
+ def format(node)
80
+ dot = super.lines.map(&:rstrip).join("\n")
81
+
82
+ generate_from_dot(dot)
83
+ end
84
+
85
+ def escape_html_chars(text)
86
+ text
87
+ .gsub(/</, "&#60;")
88
+ .gsub(/>/, "&#62;")
89
+ .gsub(/\[/, "&#91;")
90
+ .gsub(/\]/, "&#93;")
91
+ end
92
+
93
+ def format_field(node)
94
+ symbol = ACCESS_SYMBOLS[node.visibility]
95
+ result = "#{symbol}#{node.name}"
96
+ if node.type
97
+ keyword = node.keyword ? "«#{node.keyword}»" : ""
98
+ result += " : #{keyword}#{node.type}"
99
+ end
100
+ if node.cardinality
101
+ result += "[#{node.cardinality[:min]}..#{node.cardinality[:max]}]"
102
+ end
103
+ result = escape_html_chars(result)
104
+ result = "<U>#{result}</U>" if node.static
105
+
106
+ result
107
+ end
108
+
109
+ def format_method(node)
110
+ symbol = ACCESS_SYMBOLS[node.access]
111
+ result = "#{symbol} #{node.name}"
112
+ if node.arguments
113
+ arguments = node.arguments.map do |argument|
114
+ "#{argument.name}#{" : #{argument.type}" if argument.type}"
115
+ end.join(", ")
116
+ end
117
+
118
+ result << "(#{arguments})"
119
+ result << " : #{node.type}" if node.type
120
+ result = "<U>#{result}</U>" if node.static
121
+ result = "<I>#{result}</I>" if node.abstract
122
+
123
+ result
124
+ end
125
+
126
+ def format_relationship(node)
127
+ graph_parent_name = generate_graph_name(node.owner_end)
128
+ graph_node_name = generate_graph_name(node.member_end)
129
+ attributes = generate_graph_relationship_attributes(node)
130
+ graph_attributes = " [#{attributes}]" unless attributes.empty?
131
+
132
+ %{#{graph_parent_name} -> #{graph_node_name}#{graph_attributes}}
133
+ end
134
+
135
+ def generate_graph_relationship_attributes(node)
136
+ attributes = Attributes.new
137
+ if %w[dependency realizes].include?(node.member_end_type)
138
+ attributes["style"] = "dashed"
139
+ end
140
+ attributes["dir"] = if node.owner_end_type && node.member_end_type
141
+ "both"
142
+ elsif node.owner_end_type
143
+ "back"
144
+ else
145
+ "direct"
146
+ end
147
+ attributes["label"] = node.action if node.action
148
+ if node.owner_end_attribute_name
149
+ attributes["headlabel"] = format_label(
150
+ node.owner_end_attribute_name,
151
+ node.owner_end_cardinality
152
+ )
153
+ end
154
+ if node.member_end_attribute_name
155
+ attributes["taillabel"] = format_label(
156
+ node.member_end_attribute_name,
157
+ node.member_end_cardinality
158
+ )
159
+ end
160
+
161
+ attributes["arrowtail"] = case node.owner_end_type
162
+ when "composition"
163
+ "diamond"
164
+ when "aggregation"
165
+ "odiamond"
166
+ when "direct"
167
+ "vee"
168
+ else
169
+ "onormal"
170
+ end
171
+
172
+ attributes["arrowhead"] = case node.member_end_type
173
+ when "composition"
174
+ "diamond"
175
+ when "aggregation"
176
+ "odiamond"
177
+ when "direct"
178
+ "vee"
179
+ else
180
+ "onormal"
181
+ end
182
+ # swap labels and arrows if `dir` eq to `back`
183
+ if attributes["dir"] == "back"
184
+ attributes["arrowhead"], attributes["arrowtail"] =
185
+ [attributes["arrowtail"], attributes["arrowhead"]]
186
+ attributes["headlabel"], attributes["taillabel"] =
187
+ [attributes["taillabel"], attributes["headlabel"]]
188
+ end
189
+ attributes
190
+ end
191
+
192
+ def format_label(name, cardinality = {})
193
+ res = "+#{name}"
194
+ if cardinality.nil? ||
195
+ (cardinality["min"].nil? || cardinality["max"].nil?)
196
+ return res
197
+ end
198
+
199
+ "#{res} #{cardinality['min']}..#{cardinality['max']}"
200
+ end
201
+
202
+ def format_member_rows(members, hide_members)
203
+ unless !hide_members && members && members.length.positive?
204
+ return <<~HEREDOC.chomp
205
+ <TABLE BORDER="0" CELLPADDING="0" CELLSPACING="0">
206
+ <TR><TD ALIGN="LEFT"></TD></TR>
207
+ </TABLE>
208
+ HEREDOC
209
+ end
210
+
211
+ field_rows = members.map do |field|
212
+ %{<TR><TD ALIGN="LEFT">#{format_field(field)}</TD></TR>}
213
+ end
214
+ field_table = <<~HEREDOC.chomp
215
+ <TABLE BORDER="0" CELLPADDING="0" CELLSPACING="0">
216
+ #{field_rows.map { |row| ' ' * 10 + row }.join("\n")}
217
+ </TABLE>
218
+ HEREDOC
219
+ field_table << "\n" << " " * 6
220
+ field_table
221
+ end
222
+
223
+ def format_class(node, hide_members)
224
+ name = ["<B>#{node.name}</B>"]
225
+ name.unshift("«#{node.keyword}»") if node.keyword
226
+ name_html = <<~HEREDOC
227
+ <TABLE BORDER="0" CELLPADDING="0" CELLSPACING="0">
228
+ #{name.map { |n| %(<TR><TD ALIGN="CENTER">#{n}</TD></TR>) }.join('\n')}
229
+ </TABLE>
230
+ HEREDOC
231
+
232
+ field_table = format_member_rows(node.attributes, hide_members)
233
+ method_table = format_member_rows(node.methods, hide_members)
234
+ table_body = [name_html, field_table, method_table].map do |type|
235
+ next if type.nil?
236
+
237
+ <<~TEXT
238
+ <TR>
239
+ <TD>#{type}</TD>
240
+ </TR>
241
+ TEXT
242
+ end
243
+
244
+ <<~HEREDOC.chomp
245
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="10">
246
+ #{table_body.compact.join("\n")}
247
+ </TABLE>
248
+ HEREDOC
249
+ end
250
+
251
+ def format_document(node)
252
+ @fontname = node.fontname || DEFAULT_CLASS_FONT
253
+ @node["fontname"] = "#{@fontname}-bold"
254
+
255
+ if node.fidelity
256
+ hide_members = node.fidelity["hideMembers"]
257
+ hide_other_classes = node.fidelity["hideOtherClasses"]
258
+ end
259
+ classes = (node.classes +
260
+ node.enums +
261
+ node.data_types +
262
+ node.primitives).map do |class_node|
263
+ graph_node_name = generate_graph_name(class_node.name)
264
+
265
+ <<~HEREDOC
266
+ #{graph_node_name} [
267
+ shape="plain"
268
+ fontname="#{@fontname || DEFAULT_CLASS_FONT}"
269
+ label=<#{format_class(class_node, hide_members)}>]
270
+ HEREDOC
271
+ end.join("\n")
272
+ associations = node.classes.map(&:associations).compact.flatten +
273
+ node.associations
274
+ if node.groups
275
+ associations = sort_by_document_groupping(node.groups,
276
+ associations)
277
+ end
278
+ classes_names = node.classes.map(&:name)
279
+ associations = associations.map do |assoc_node|
280
+ if hide_other_classes &&
281
+ !classes_names.include?(assoc_node.member_end)
282
+ next
283
+ end
284
+
285
+ format_relationship(assoc_node)
286
+ end.join("\n")
287
+
288
+ classes = classes.lines.map { |line| " #{line}" }.join.chomp
289
+ associations = associations
290
+ .lines.map { |line| " #{line}" }.join.chomp
291
+
292
+ <<~HEREDOC
293
+ digraph G {
294
+ graph [#{@graph}]
295
+ edge [#{@edge}]
296
+ node [#{@node}]
297
+
298
+ #{classes}
299
+
300
+ #{associations}
301
+ }
302
+ HEREDOC
303
+ end
304
+
305
+ protected
306
+
307
+ def sort_by_document_groupping(groups, associations)
308
+ result = []
309
+ groups.each do |batch|
310
+ batch.each do |group_name|
311
+ associations
312
+ .select { |assc| assc.owner_end == group_name }
313
+ .each do |association|
314
+ result.push(association) unless result.include?(association)
315
+ end
316
+ end
317
+ end
318
+ associations.each do |association|
319
+ result.push(association) unless result.include?(association)
320
+ end
321
+ result
322
+ end
323
+
324
+ def generate_from_dot(input)
325
+ Lutaml::Layout::GraphVizEngine.new(input: input).render(@type)
326
+ end
327
+
328
+ def generate_graph_name(name)
329
+ name.gsub(/[^0-9a-zA-Z]/i, "")
330
+ end
331
+ end
332
+ end
333
+ end
334
+ end