Added rendering of implementations

This commit is contained in:
Sven Heidemann 2022-01-16 20:11:13 +01:00
parent c53b3b7669
commit 5ba24cafaf
6 changed files with 132 additions and 23 deletions

View File

@ -64,9 +64,8 @@ class Application(ApplicationABC):
Console.write_line(f'Found {classes.count()} classes')
implementations: List[ClassImplementation] = self._parser.parse_implementations(classes)
Console.write_line(f'Found {implementations.count()} implementations')
Console.write_line(implementations)
# self._console_output(classes)
xml = self._umlet_creator.generate_xml(classes)
xml = self._umlet_creator.generate_xml(classes, implementations)
except Exception as e:
Console.error('Parsing failed', f'{e} -> {traceback.format_exc()}')
exit()

View File

@ -1,5 +1,6 @@
from abc import ABC, abstractmethod
from py_to_uxf_core.model.class_implementation import ClassImplementation
from py_to_uxf_core.model.python_class import PythonClass
@ -9,4 +10,4 @@ class UmletCreatorABC(ABC):
def __init__(self): pass
@abstractmethod
def generate_xml(self, classes: list[PythonClass]) -> str: pass
def generate_xml(self, classes: list[PythonClass], implementations: list[ClassImplementation]) -> str: pass

View File

@ -19,3 +19,9 @@ class Dimension:
@height.setter
def height(self, value: int):
self._height = value
def __str__(self):
return f'(w:{self._width}, h:{self._height})'
def __repr__(self):
return f'(w:{self._width}, h:{self._height})'

View File

@ -19,3 +19,9 @@ class Position:
@y.setter
def y(self, value: int):
self._y = value
def __str__(self):
return f'(x:{self._x}, y:{self._y})'
def __repr__(self):
return f'(x:{self._x}, y:{self._y})'

View File

@ -1,3 +1,4 @@
import math
import textwrap
from cpl_core.console import Console
@ -19,6 +20,9 @@ class UMLClass:
self._position = pos
self._dimension = dim
self._attributes = ''
self._functions = ''
@property
def cls(self) -> PythonClass:
return self._cls
@ -39,21 +43,54 @@ class UMLClass:
def dimension(self, value: int):
self._dimension = value
def as_xml(self) -> str:
def implementation_as_xml(self, base_class: 'UMLClass') -> str:
base_point = Position(
base_class.position.x + int(base_class.dimension.width / 2),
base_class.position.y + base_class.dimension.height
)
sub_point = Position(
self._position.x + int(self._dimension.width / 2),
self._position.y
)
path_dimension = Dimension(
self._position.x - base_class.position.x,
sub_point.y - base_point.y
)
path_position = Position(
0,
0
)
return f"""\
<element>
<id>Relation</id>
<coordinates>
<x>{base_point.x}</x>
<y>{base_point.y}</y>
<w>0</w>
<h>0</h>
</coordinates>
<panel_attributes>lt=&lt;&lt;-
</panel_attributes>
<additional_attributes>{float(path_position.x)};{float(path_position.y)};{float(path_dimension.width)};{float(path_dimension.height)}</additional_attributes>
</element>
""".replace(' ', '').replace('\t', '')
def create_xml_body(self):
px_per_line = 16
px_per_char = 2.9
self._dimension.height += (self._cls.attributes.count() + self._cls.functions.count()) * px_per_line
self._dimension.height += int(
round((self._cls.attributes.count() + self._cls.functions.count()) * px_per_line, -1))
longest_line_length = self._dimension.width / px_per_char
attributes = ''
functions = ''
if len(self._cls.attributes) > 0:
for atr in self._cls.attributes:
attribute = f'{atr.access_modifier.value}{atr.name}: {atr.type}\n'
if len(attribute) > longest_line_length:
longest_line_length = len(attribute)
attributes += attribute
self._attributes += attribute
if len(self._cls.functions) > 0:
for func in self._cls.functions:
@ -61,10 +98,11 @@ class UMLClass:
function = f'{func.access_modifier.value}{func.name}({args}): {func.return_type}\n'
if len(function) > longest_line_length:
longest_line_length = len(function)
functions += function
self._functions += function
self._dimension.width = round(longest_line_length * px_per_char * px_per_char)
self._dimension.width = int(round(longest_line_length * px_per_char * px_per_char, -1))
def as_xml(self) -> str:
return f"""\
<element>
<id>UMLClass</id>
@ -77,9 +115,9 @@ class UMLClass:
<panel_attributes>
{self._cls.name}
--
{attributes}
{self._attributes}
--
{functions}
{self._functions}
</panel_attributes>
<additional_attributes></additional_attributes>
</element>

View File

@ -1,8 +1,8 @@
import textwrap
from cpl_core.console import Console
import classes as classes
from cpl_query.extension import List
from py_to_uxf_core.abc.umlet_creator_abc import UmletCreatorABC
from py_to_uxf_core.model.class_implementation import ClassImplementation
from py_to_uxf_core.model.dimension import Dimension
from py_to_uxf_core.model.position import Position
from py_to_uxf_core.model.python_class import PythonClass
@ -14,22 +14,81 @@ class UmletCreatorService(UmletCreatorABC):
def __init__(self):
UmletCreatorABC.__init__(self)
def generate_xml(self, classes: list[PythonClass]) -> str:
xml_cls = ''
self._padding = 30
def _sort_by_implementation(self, uml_classes: List[UMLClass], implementations: List[ClassImplementation]):
base_sub_map: dict[UMLClass, list[UMLClass]] = {}
moved_classes: list[UMLClass] = []
for implementation in implementations:
sub_class: UMLClass = uml_classes.where(lambda u: u.cls == implementation.subclass).first()
base_class: UMLClass = uml_classes.where(lambda u: u.cls == implementation.base).first()
if base_class not in base_sub_map:
base_sub_map[base_class] = []
base_sub_map[base_class].append(sub_class)
last_base = Position(self._padding, self._padding)
for base in base_sub_map:
subclasses = base_sub_map[base]
base.position.x = last_base.x
base.position.y = self._padding
last_sub = Position(base.position.x, base.position.y + base.dimension.height)
for sub_class in subclasses:
sub_class.position.x = base.position.x
sub_class.position.y = base.position.y
if sub_class.position.x <= last_sub.x:
sub_class.position.x = last_sub.x
if sub_class.position.y <= last_sub.y:
sub_class.position.y = last_sub.y + self._padding * 2
last_sub.x = sub_class.position.x + sub_class.dimension.width + self._padding
moved_classes.append(sub_class)
last_base = last_sub
last_base.x += self._padding * 2
moved_classes.append(base)
for cls in uml_classes:
if cls in moved_classes:
continue
cls.position.x = last_base.x + self._padding
last_base.x = cls.position.x + cls.dimension.width
def generate_xml(self, classes: List[PythonClass], implementations: List[ClassImplementation]) -> str:
uml_classes = List(UMLClass)
xml_classes = ''
xml_relations = ''
default_width = 80
default_height = 80
next_x = 10
next_x = self._padding
for cls in classes:
uml_cls = UMLClass(cls, Position(next_x, 10), Dimension(default_width, default_height))
uml_cls = UMLClass(cls, Position(next_x, self._padding), Dimension(default_width, default_height))
# trigger xml generation to render coordinates
uml_cls.create_xml_body()
next_x += round(uml_cls.dimension.width, -1) + self._padding
uml_classes.append(uml_cls)
self._sort_by_implementation(uml_classes, implementations)
for implementation in implementations:
sub_class: UMLClass = uml_classes.where(lambda u: u.cls == implementation.subclass).first()
base_class: UMLClass = uml_classes.where(lambda u: u.cls == implementation.base).first()
xml_relations += sub_class.implementation_as_xml(base_class)
for uml_cls in uml_classes:
# save class xml
xml_cls += uml_cls.as_xml()
next_x += round(uml_cls.dimension.width, -1) + 10
xml_classes += uml_cls.as_xml()
return f"""\
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<diagram program="umlet" version="14.1.1">
<zoom_level>10</zoom_level>
{xml_cls}
{xml_classes}
{xml_relations}
</diagram>
""".replace(' ', '').replace('\t', '')