ZahirJS commited on
Commit
6ee5ad8
·
verified ·
1 Parent(s): 7474f3d

Update class_diagram_generator.py

Browse files
Files changed (1) hide show
  1. class_diagram_generator.py +391 -35
class_diagram_generator.py CHANGED
@@ -1,3 +1,347 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import graphviz
2
  import json
3
  from tempfile import NamedTemporaryFile
@@ -199,10 +543,11 @@ def generate_class_diagram(json_input: str, output_format: str) -> str:
199
  if 'classes' not in data:
200
  raise ValueError("Missing required field: classes")
201
 
 
202
  dot = graphviz.Digraph(comment='Class Diagram')
203
- dot.attr(rankdir='TB', bgcolor='white', pad='0.5')
204
- dot.attr('node', shape='box', style='filled', fillcolor='white', color='black', fontname='Arial', fontsize='10')
205
- dot.attr('edge', color='black', fontname='Arial', fontsize='9')
206
 
207
  classes = data.get('classes', [])
208
  relationships = data.get('relationships', [])
@@ -216,19 +561,24 @@ def generate_class_diagram(json_input: str, output_format: str) -> str:
216
  if not class_name:
217
  continue
218
 
219
- label_parts = []
 
220
 
 
221
  if class_type == 'abstract':
222
- label_parts.append(f"<<abstract>>\\n{class_name}")
223
  elif class_type == 'interface':
224
- label_parts.append(f"<<interface>>\\n{class_name}")
225
  elif class_type == 'enum':
226
- label_parts.append(f"<<enumeration>>\\n{class_name}")
227
  else:
228
- label_parts.append(class_name)
 
 
 
229
 
 
230
  if attributes:
231
- attr_lines = []
232
  for attr in attributes:
233
  visibility = attr.get('visibility', '+')
234
  name = attr.get('name', '')
@@ -239,15 +589,18 @@ def generate_class_diagram(json_input: str, output_format: str) -> str:
239
  if attr_type:
240
  line += f" : {attr_type}"
241
  if is_static:
242
- line += " [static]"
243
 
244
- attr_lines.append(line)
245
-
246
- if attr_lines:
247
- label_parts.append("\\n".join(attr_lines))
 
 
 
248
 
 
249
  if methods:
250
- method_lines = []
251
  for method in methods:
252
  visibility = method.get('visibility', '+')
253
  name = method.get('name', '')
@@ -267,29 +620,21 @@ def generate_class_diagram(json_input: str, output_format: str) -> str:
267
  line += f") : {return_type}"
268
 
269
  if is_static:
270
- line += " [static]"
271
  if is_abstract:
272
- line += " [abstract]"
273
 
274
- method_lines.append(line)
275
-
276
- if method_lines:
277
- label_parts.append("\\n".join(method_lines))
278
-
279
- label = "\\n\\n".join(label_parts)
280
-
281
- if class_type == 'interface':
282
- fillcolor = '#f5f5f5'
283
- style = 'filled,dashed'
284
- elif class_type == 'abstract':
285
- fillcolor = '#eeeeee'
286
- style = 'filled'
287
  else:
288
- fillcolor = 'white'
289
- style = 'filled'
290
 
291
- dot.node(class_name, label, fillcolor=fillcolor, style=style)
 
 
 
292
 
 
293
  for relationship in relationships:
294
  from_class = relationship.get('from')
295
  to_class = relationship.get('to')
@@ -301,7 +646,10 @@ def generate_class_diagram(json_input: str, output_format: str) -> str:
301
  if not from_class or not to_class:
302
  continue
303
 
304
- edge_attrs = {}
 
 
 
305
 
306
  if label:
307
  edge_attrs['label'] = label
@@ -312,27 +660,35 @@ def generate_class_diagram(json_input: str, output_format: str) -> str:
312
  if multiplicity_to:
313
  edge_attrs['headlabel'] = multiplicity_to
314
 
 
315
  if rel_type == 'inheritance':
316
  edge_attrs['arrowhead'] = 'empty'
 
317
  elif rel_type == 'composition':
318
  edge_attrs['arrowhead'] = 'normal'
319
  edge_attrs['arrowtail'] = 'diamond'
320
  edge_attrs['dir'] = 'both'
 
321
  elif rel_type == 'aggregation':
322
  edge_attrs['arrowhead'] = 'normal'
323
  edge_attrs['arrowtail'] = 'odiamond'
324
  edge_attrs['dir'] = 'both'
 
325
  elif rel_type == 'realization':
326
  edge_attrs['arrowhead'] = 'empty'
327
  edge_attrs['style'] = 'dashed'
 
328
  elif rel_type == 'dependency':
329
  edge_attrs['arrowhead'] = 'normal'
330
  edge_attrs['style'] = 'dashed'
331
- else:
 
332
  edge_attrs['arrowhead'] = 'normal'
 
333
 
334
  dot.edge(from_class, to_class, **edge_attrs)
335
 
 
336
  with NamedTemporaryFile(delete=False, suffix=f'.{output_format}') as tmp:
337
  dot.render(tmp.name, format=output_format, cleanup=True)
338
  return f"{tmp.name}.{output_format}"
 
1
+ # import graphviz
2
+ # import json
3
+ # from tempfile import NamedTemporaryFile
4
+ # import os
5
+
6
+ # def generate_class_diagram(json_input: str, output_format: str) -> str:
7
+ # """
8
+ # Generates a class diagram from JSON input.
9
+
10
+ # Args:
11
+ # json_input (str): A JSON string describing the class diagram structure.
12
+ # It must follow the Expected JSON Format Example below.
13
+
14
+ # Expected JSON Format Example:
15
+ # {
16
+ # "classes": [
17
+ # {
18
+ # "name": "Vehicle",
19
+ # "type": "abstract",
20
+ # "attributes": [
21
+ # {"name": "id", "type": "String", "visibility": "-"},
22
+ # {"name": "brand", "type": "String", "visibility": "#"},
23
+ # {"name": "model", "type": "String", "visibility": "#"},
24
+ # {"name": "year", "type": "int", "visibility": "#"},
25
+ # {"name": "price", "type": "double", "visibility": "+"},
26
+ # {"name": "vehicleCount", "type": "int", "visibility": "+", "static": true}
27
+ # ],
28
+ # "methods": [
29
+ # {"name": "Vehicle", "parameters": [{"name": "brand", "type": "String"}, {"name": "model", "type": "String"}], "return_type": "Vehicle", "visibility": "+"},
30
+ # {"name": "startEngine", "return_type": "void", "visibility": "+", "abstract": true},
31
+ # {"name": "stopEngine", "return_type": "void", "visibility": "+"},
32
+ # {"name": "getPrice", "return_type": "double", "visibility": "+"},
33
+ # {"name": "setPrice", "parameters": [{"name": "price", "type": "double"}], "return_type": "void", "visibility": "+"},
34
+ # {"name": "getTotalVehicles", "return_type": "int", "visibility": "+", "static": true}
35
+ # ]
36
+ # },
37
+ # {
38
+ # "name": "Car",
39
+ # "type": "class",
40
+ # "attributes": [
41
+ # {"name": "doors", "type": "int", "visibility": "-"},
42
+ # {"name": "transmission", "type": "TransmissionType", "visibility": "-"},
43
+ # {"name": "fuelType", "type": "FuelType", "visibility": "-"}
44
+ # ],
45
+ # "methods": [
46
+ # {"name": "Car", "parameters": [{"name": "brand", "type": "String"}, {"name": "model", "type": "String"}, {"name": "doors", "type": "int"}], "return_type": "Car", "visibility": "+"},
47
+ # {"name": "startEngine", "return_type": "void", "visibility": "+"},
48
+ # {"name": "openTrunk", "return_type": "void", "visibility": "+"},
49
+ # {"name": "getDoors", "return_type": "int", "visibility": "+"},
50
+ # {"name": "setTransmission", "parameters": [{"name": "transmission", "type": "TransmissionType"}], "return_type": "void", "visibility": "+"}
51
+ # ]
52
+ # },
53
+ # {
54
+ # "name": "Motorcycle",
55
+ # "type": "class",
56
+ # "attributes": [
57
+ # {"name": "engineSize", "type": "int", "visibility": "-"},
58
+ # {"name": "hasWindshield", "type": "boolean", "visibility": "-"}
59
+ # ],
60
+ # "methods": [
61
+ # {"name": "Motorcycle", "parameters": [{"name": "brand", "type": "String"}, {"name": "model", "type": "String"}], "return_type": "Motorcycle", "visibility": "+"},
62
+ # {"name": "startEngine", "return_type": "void", "visibility": "+"},
63
+ # {"name": "wheelie", "return_type": "void", "visibility": "+"},
64
+ # {"name": "getEngineSize", "return_type": "int", "visibility": "+"}
65
+ # ]
66
+ # },
67
+ # {
68
+ # "name": "Engine",
69
+ # "type": "class",
70
+ # "attributes": [
71
+ # {"name": "horsepower", "type": "int", "visibility": "-"},
72
+ # {"name": "cylinders", "type": "int", "visibility": "-"},
73
+ # {"name": "fuelType", "type": "FuelType", "visibility": "-"}
74
+ # ],
75
+ # "methods": [
76
+ # {"name": "Engine", "parameters": [{"name": "horsepower", "type": "int"}, {"name": "cylinders", "type": "int"}], "return_type": "Engine", "visibility": "+"},
77
+ # {"name": "start", "return_type": "boolean", "visibility": "+"},
78
+ # {"name": "stop", "return_type": "void", "visibility": "+"},
79
+ # {"name": "getHorsepower", "return_type": "int", "visibility": "+"}
80
+ # ]
81
+ # },
82
+ # {
83
+ # "name": "TransmissionType",
84
+ # "type": "enum",
85
+ # "attributes": [
86
+ # {"name": "MANUAL", "type": "TransmissionType", "visibility": "+", "static": true},
87
+ # {"name": "AUTOMATIC", "type": "TransmissionType", "visibility": "+", "static": true},
88
+ # {"name": "CVT", "type": "TransmissionType", "visibility": "+", "static": true}
89
+ # ],
90
+ # "methods": []
91
+ # },
92
+ # {
93
+ # "name": "FuelType",
94
+ # "type": "enum",
95
+ # "attributes": [
96
+ # {"name": "GASOLINE", "type": "FuelType", "visibility": "+", "static": true},
97
+ # {"name": "DIESEL", "type": "FuelType", "visibility": "+", "static": true},
98
+ # {"name": "ELECTRIC", "type": "FuelType", "visibility": "+", "static": true},
99
+ # {"name": "HYBRID", "type": "FuelType", "visibility": "+", "static": true}
100
+ # ],
101
+ # "methods": []
102
+ # },
103
+ # {
104
+ # "name": "VehicleService",
105
+ # "type": "interface",
106
+ # "attributes": [],
107
+ # "methods": [
108
+ # {"name": "maintenance", "parameters": [{"name": "vehicle", "type": "Vehicle"}], "return_type": "void", "visibility": "+", "abstract": true},
109
+ # {"name": "repair", "parameters": [{"name": "vehicle", "type": "Vehicle"}, {"name": "issue", "type": "String"}], "return_type": "boolean", "visibility": "+", "abstract": true},
110
+ # {"name": "inspectVehicle", "parameters": [{"name": "vehicle", "type": "Vehicle"}], "return_type": "InspectionReport", "visibility": "+", "abstract": true}
111
+ # ]
112
+ # },
113
+ # {
114
+ # "name": "GarageService",
115
+ # "type": "class",
116
+ # "attributes": [
117
+ # {"name": "garageName", "type": "String", "visibility": "-"},
118
+ # {"name": "location", "type": "String", "visibility": "-"}
119
+ # ],
120
+ # "methods": [
121
+ # {"name": "GarageService", "parameters": [{"name": "name", "type": "String"}], "return_type": "GarageService", "visibility": "+"},
122
+ # {"name": "maintenance", "parameters": [{"name": "vehicle", "type": "Vehicle"}], "return_type": "void", "visibility": "+"},
123
+ # {"name": "repair", "parameters": [{"name": "vehicle", "type": "Vehicle"}, {"name": "issue", "type": "String"}], "return_type": "boolean", "visibility": "+"},
124
+ # {"name": "inspectVehicle", "parameters": [{"name": "vehicle", "type": "Vehicle"}], "return_type": "InspectionReport", "visibility": "+"}
125
+ # ]
126
+ # }
127
+ # ],
128
+ # "relationships": [
129
+ # {
130
+ # "from": "Car",
131
+ # "to": "Vehicle",
132
+ # "type": "inheritance",
133
+ # "label": "extends"
134
+ # },
135
+ # {
136
+ # "from": "Motorcycle",
137
+ # "to": "Vehicle",
138
+ # "type": "inheritance",
139
+ # "label": "extends"
140
+ # },
141
+ # {
142
+ # "from": "Car",
143
+ # "to": "Engine",
144
+ # "type": "composition",
145
+ # "label": "has",
146
+ # "multiplicity_from": "1",
147
+ # "multiplicity_to": "1"
148
+ # },
149
+ # {
150
+ # "from": "Motorcycle",
151
+ # "to": "Engine",
152
+ # "type": "composition",
153
+ # "label": "has",
154
+ # "multiplicity_from": "1",
155
+ # "multiplicity_to": "1"
156
+ # },
157
+ # {
158
+ # "from": "Car",
159
+ # "to": "TransmissionType",
160
+ # "type": "association",
161
+ # "label": "uses",
162
+ # "multiplicity_from": "1",
163
+ # "multiplicity_to": "1"
164
+ # },
165
+ # {
166
+ # "from": "Vehicle",
167
+ # "to": "FuelType",
168
+ # "type": "association",
169
+ # "label": "uses",
170
+ # "multiplicity_from": "1",
171
+ # "multiplicity_to": "1"
172
+ # },
173
+ # {
174
+ # "from": "GarageService",
175
+ # "to": "VehicleService",
176
+ # "type": "realization",
177
+ # "label": "implements"
178
+ # },
179
+ # {
180
+ # "from": "GarageService",
181
+ # "to": "Vehicle",
182
+ # "type": "dependency",
183
+ # "label": "services",
184
+ # "multiplicity_from": "1",
185
+ # "multiplicity_to": "*"
186
+ # }
187
+ # ]
188
+ # }
189
+
190
+ # Returns:
191
+ # str: The filepath to the generated PNG image file.
192
+ # """
193
+ # try:
194
+ # if not json_input.strip():
195
+ # return "Error: Empty input"
196
+
197
+ # data = json.loads(json_input)
198
+
199
+ # if 'classes' not in data:
200
+ # raise ValueError("Missing required field: classes")
201
+
202
+ # dot = graphviz.Digraph(comment='Class Diagram')
203
+ # dot.attr(rankdir='TB', bgcolor='white', pad='0.5')
204
+ # dot.attr('node', shape='box', style='filled', fillcolor='white', color='black', fontname='Arial', fontsize='10')
205
+ # dot.attr('edge', color='black', fontname='Arial', fontsize='9')
206
+
207
+ # classes = data.get('classes', [])
208
+ # relationships = data.get('relationships', [])
209
+
210
+ # for cls in classes:
211
+ # class_name = cls.get('name')
212
+ # class_type = cls.get('type', 'class')
213
+ # attributes = cls.get('attributes', [])
214
+ # methods = cls.get('methods', [])
215
+
216
+ # if not class_name:
217
+ # continue
218
+
219
+ # label_parts = []
220
+
221
+ # if class_type == 'abstract':
222
+ # label_parts.append(f"<<abstract>>\\n{class_name}")
223
+ # elif class_type == 'interface':
224
+ # label_parts.append(f"<<interface>>\\n{class_name}")
225
+ # elif class_type == 'enum':
226
+ # label_parts.append(f"<<enumeration>>\\n{class_name}")
227
+ # else:
228
+ # label_parts.append(class_name)
229
+
230
+ # if attributes:
231
+ # attr_lines = []
232
+ # for attr in attributes:
233
+ # visibility = attr.get('visibility', '+')
234
+ # name = attr.get('name', '')
235
+ # attr_type = attr.get('type', '')
236
+ # is_static = attr.get('static', False)
237
+
238
+ # line = f"{visibility} {name}"
239
+ # if attr_type:
240
+ # line += f" : {attr_type}"
241
+ # if is_static:
242
+ # line += " [static]"
243
+
244
+ # attr_lines.append(line)
245
+
246
+ # if attr_lines:
247
+ # label_parts.append("\\n".join(attr_lines))
248
+
249
+ # if methods:
250
+ # method_lines = []
251
+ # for method in methods:
252
+ # visibility = method.get('visibility', '+')
253
+ # name = method.get('name', '')
254
+ # parameters = method.get('parameters', [])
255
+ # return_type = method.get('return_type', 'void')
256
+ # is_static = method.get('static', False)
257
+ # is_abstract = method.get('abstract', False)
258
+
259
+ # line = f"{visibility} {name}("
260
+ # if parameters:
261
+ # params = []
262
+ # for param in parameters:
263
+ # param_name = param.get('name', '')
264
+ # param_type = param.get('type', '')
265
+ # params.append(f"{param_name}: {param_type}")
266
+ # line += ", ".join(params)
267
+ # line += f") : {return_type}"
268
+
269
+ # if is_static:
270
+ # line += " [static]"
271
+ # if is_abstract:
272
+ # line += " [abstract]"
273
+
274
+ # method_lines.append(line)
275
+
276
+ # if method_lines:
277
+ # label_parts.append("\\n".join(method_lines))
278
+
279
+ # label = "\\n\\n".join(label_parts)
280
+
281
+ # if class_type == 'interface':
282
+ # fillcolor = '#f5f5f5'
283
+ # style = 'filled,dashed'
284
+ # elif class_type == 'abstract':
285
+ # fillcolor = '#eeeeee'
286
+ # style = 'filled'
287
+ # else:
288
+ # fillcolor = 'white'
289
+ # style = 'filled'
290
+
291
+ # dot.node(class_name, label, fillcolor=fillcolor, style=style)
292
+
293
+ # for relationship in relationships:
294
+ # from_class = relationship.get('from')
295
+ # to_class = relationship.get('to')
296
+ # rel_type = relationship.get('type', 'association')
297
+ # label = relationship.get('label', '')
298
+ # multiplicity_from = relationship.get('multiplicity_from', '')
299
+ # multiplicity_to = relationship.get('multiplicity_to', '')
300
+
301
+ # if not from_class or not to_class:
302
+ # continue
303
+
304
+ # edge_attrs = {}
305
+
306
+ # if label:
307
+ # edge_attrs['label'] = label
308
+
309
+ # if multiplicity_from:
310
+ # edge_attrs['taillabel'] = multiplicity_from
311
+
312
+ # if multiplicity_to:
313
+ # edge_attrs['headlabel'] = multiplicity_to
314
+
315
+ # if rel_type == 'inheritance':
316
+ # edge_attrs['arrowhead'] = 'empty'
317
+ # elif rel_type == 'composition':
318
+ # edge_attrs['arrowhead'] = 'normal'
319
+ # edge_attrs['arrowtail'] = 'diamond'
320
+ # edge_attrs['dir'] = 'both'
321
+ # elif rel_type == 'aggregation':
322
+ # edge_attrs['arrowhead'] = 'normal'
323
+ # edge_attrs['arrowtail'] = 'odiamond'
324
+ # edge_attrs['dir'] = 'both'
325
+ # elif rel_type == 'realization':
326
+ # edge_attrs['arrowhead'] = 'empty'
327
+ # edge_attrs['style'] = 'dashed'
328
+ # elif rel_type == 'dependency':
329
+ # edge_attrs['arrowhead'] = 'normal'
330
+ # edge_attrs['style'] = 'dashed'
331
+ # else:
332
+ # edge_attrs['arrowhead'] = 'normal'
333
+
334
+ # dot.edge(from_class, to_class, **edge_attrs)
335
+
336
+ # with NamedTemporaryFile(delete=False, suffix=f'.{output_format}') as tmp:
337
+ # dot.render(tmp.name, format=output_format, cleanup=True)
338
+ # return f"{tmp.name}.{output_format}"
339
+
340
+ # except json.JSONDecodeError:
341
+ # return "Error: Invalid JSON format"
342
+ # except Exception as e:
343
+ # return f"Error: {str(e)}"
344
+
345
  import graphviz
346
  import json
347
  from tempfile import NamedTemporaryFile
 
543
  if 'classes' not in data:
544
  raise ValueError("Missing required field: classes")
545
 
546
+ # Configuración del diagrama con mejor espaciado
547
  dot = graphviz.Digraph(comment='Class Diagram')
548
+ dot.attr(rankdir='TB', bgcolor='white', pad='1.0', nodesep='1.5', ranksep='2.0')
549
+ dot.attr('node', shape='plaintext', fontname='Arial', fontsize='11')
550
+ dot.attr('edge', color='black', fontname='Arial', fontsize='9', minlen='2')
551
 
552
  classes = data.get('classes', [])
553
  relationships = data.get('relationships', [])
 
561
  if not class_name:
562
  continue
563
 
564
+ # Crear tabla HTML para estructura de clase
565
+ html_label = '<TABLE BORDER="1" CELLBORDER="0" CELLSPACING="0" CELLPADDING="5" BGCOLOR="white">'
566
 
567
+ # Header con nombre de clase y estereotipo
568
  if class_type == 'abstract':
569
+ html_label += f'<TR><TD ALIGN="CENTER"><B>&lt;&lt;abstract&gt;&gt;<BR/>{class_name}</B></TD></TR>'
570
  elif class_type == 'interface':
571
+ html_label += f'<TR><TD ALIGN="CENTER"><B>&lt;&lt;interface&gt;&gt;<BR/>{class_name}</B></TD></TR>'
572
  elif class_type == 'enum':
573
+ html_label += f'<TR><TD ALIGN="CENTER"><B>&lt;&lt;enumeration&gt;&gt;<BR/>{class_name}</B></TD></TR>'
574
  else:
575
+ html_label += f'<TR><TD ALIGN="CENTER"><B>{class_name}</B></TD></TR>'
576
+
577
+ # Línea separadora después del nombre
578
+ html_label += '<HR/>'
579
 
580
+ # Sección de atributos
581
  if attributes:
 
582
  for attr in attributes:
583
  visibility = attr.get('visibility', '+')
584
  name = attr.get('name', '')
 
589
  if attr_type:
590
  line += f" : {attr_type}"
591
  if is_static:
592
+ line = f"<U>{line}</U>" # Subrayado para elementos estáticos
593
 
594
+ html_label += f'<TR><TD ALIGN="LEFT">{line}</TD></TR>'
595
+ else:
596
+ # Espacio vacío si no hay atributos
597
+ html_label += '<TR><TD ALIGN="LEFT"> </TD></TR>'
598
+
599
+ # Línea separadora entre atributos y métodos
600
+ html_label += '<HR/>'
601
 
602
+ # Sección de métodos
603
  if methods:
 
604
  for method in methods:
605
  visibility = method.get('visibility', '+')
606
  name = method.get('name', '')
 
620
  line += f") : {return_type}"
621
 
622
  if is_static:
623
+ line = f"<U>{line}</U>" # Subrayado para métodos estáticos
624
  if is_abstract:
625
+ line = f"<I>{line}</I>" # Cursiva para métodos abstractos
626
 
627
+ html_label += f'<TR><TD ALIGN="LEFT">{line}</TD></TR>'
 
 
 
 
 
 
 
 
 
 
 
 
628
  else:
629
+ # Espacio vacío si no hay métodos
630
+ html_label += '<TR><TD ALIGN="LEFT"> </TD></TR>'
631
 
632
+ html_label += '</TABLE>'
633
+
634
+ # Agregar nodo con la tabla HTML
635
+ dot.node(class_name, f'<{html_label}>')
636
 
637
+ # Procesar relaciones con líneas rectas
638
  for relationship in relationships:
639
  from_class = relationship.get('from')
640
  to_class = relationship.get('to')
 
646
  if not from_class or not to_class:
647
  continue
648
 
649
+ edge_attrs = {
650
+ 'splines': 'ortho', # Líneas rectas (ortogonales)
651
+ 'concentrate': 'true'
652
+ }
653
 
654
  if label:
655
  edge_attrs['label'] = label
 
660
  if multiplicity_to:
661
  edge_attrs['headlabel'] = multiplicity_to
662
 
663
+ # Configurar estilos de flecha según el tipo de relación
664
  if rel_type == 'inheritance':
665
  edge_attrs['arrowhead'] = 'empty'
666
+ edge_attrs['color'] = 'black'
667
  elif rel_type == 'composition':
668
  edge_attrs['arrowhead'] = 'normal'
669
  edge_attrs['arrowtail'] = 'diamond'
670
  edge_attrs['dir'] = 'both'
671
+ edge_attrs['color'] = 'black'
672
  elif rel_type == 'aggregation':
673
  edge_attrs['arrowhead'] = 'normal'
674
  edge_attrs['arrowtail'] = 'odiamond'
675
  edge_attrs['dir'] = 'both'
676
+ edge_attrs['color'] = 'black'
677
  elif rel_type == 'realization':
678
  edge_attrs['arrowhead'] = 'empty'
679
  edge_attrs['style'] = 'dashed'
680
+ edge_attrs['color'] = 'black'
681
  elif rel_type == 'dependency':
682
  edge_attrs['arrowhead'] = 'normal'
683
  edge_attrs['style'] = 'dashed'
684
+ edge_attrs['color'] = 'black'
685
+ else: # association
686
  edge_attrs['arrowhead'] = 'normal'
687
+ edge_attrs['color'] = 'black'
688
 
689
  dot.edge(from_class, to_class, **edge_attrs)
690
 
691
+ # Renderizar el diagrama
692
  with NamedTemporaryFile(delete=False, suffix=f'.{output_format}') as tmp:
693
  dot.render(tmp.name, format=output_format, cleanup=True)
694
  return f"{tmp.name}.{output_format}"