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') Console.write_line(f'Found {classes.count()} classes')
implementations: List[ClassImplementation] = self._parser.parse_implementations(classes) implementations: List[ClassImplementation] = self._parser.parse_implementations(classes)
Console.write_line(f'Found {implementations.count()} implementations') Console.write_line(f'Found {implementations.count()} implementations')
Console.write_line(implementations)
# self._console_output(classes) # self._console_output(classes)
xml = self._umlet_creator.generate_xml(classes) xml = self._umlet_creator.generate_xml(classes, implementations)
except Exception as e: except Exception as e:
Console.error('Parsing failed', f'{e} -> {traceback.format_exc()}') Console.error('Parsing failed', f'{e} -> {traceback.format_exc()}')
exit() exit()

View File

@ -1,5 +1,6 @@
from abc import ABC, abstractmethod 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 from py_to_uxf_core.model.python_class import PythonClass
@ -9,4 +10,4 @@ class UmletCreatorABC(ABC):
def __init__(self): pass def __init__(self): pass
@abstractmethod @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 @height.setter
def height(self, value: int): def height(self, value: int):
self._height = value 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 @y.setter
def y(self, value: int): def y(self, value: int):
self._y = value 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 import textwrap
from cpl_core.console import Console from cpl_core.console import Console
@ -19,6 +20,9 @@ class UMLClass:
self._position = pos self._position = pos
self._dimension = dim self._dimension = dim
self._attributes = ''
self._functions = ''
@property @property
def cls(self) -> PythonClass: def cls(self) -> PythonClass:
return self._cls return self._cls
@ -39,21 +43,54 @@ class UMLClass:
def dimension(self, value: int): def dimension(self, value: int):
self._dimension = value 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_line = 16
px_per_char = 2.9 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 longest_line_length = self._dimension.width / px_per_char
attributes = ''
functions = ''
if len(self._cls.attributes) > 0: if len(self._cls.attributes) > 0:
for atr in self._cls.attributes: for atr in self._cls.attributes:
attribute = f'{atr.access_modifier.value}{atr.name}: {atr.type}\n' attribute = f'{atr.access_modifier.value}{atr.name}: {atr.type}\n'
if len(attribute) > longest_line_length: if len(attribute) > longest_line_length:
longest_line_length = len(attribute) longest_line_length = len(attribute)
attributes += attribute self._attributes += attribute
if len(self._cls.functions) > 0: if len(self._cls.functions) > 0:
for func in self._cls.functions: 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' function = f'{func.access_modifier.value}{func.name}({args}): {func.return_type}\n'
if len(function) > longest_line_length: if len(function) > longest_line_length:
longest_line_length = len(function) 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"""\ return f"""\
<element> <element>
<id>UMLClass</id> <id>UMLClass</id>
@ -77,9 +115,9 @@ class UMLClass:
<panel_attributes> <panel_attributes>
{self._cls.name} {self._cls.name}
-- --
{attributes} {self._attributes}
-- --
{functions} {self._functions}
</panel_attributes> </panel_attributes>
<additional_attributes></additional_attributes> <additional_attributes></additional_attributes>
</element> </element>

View File

@ -1,8 +1,8 @@
import textwrap import classes as classes
from cpl_query.extension import List
from cpl_core.console import Console
from py_to_uxf_core.abc.umlet_creator_abc import UmletCreatorABC 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.dimension import Dimension
from py_to_uxf_core.model.position import Position from py_to_uxf_core.model.position import Position
from py_to_uxf_core.model.python_class import PythonClass from py_to_uxf_core.model.python_class import PythonClass
@ -14,22 +14,81 @@ class UmletCreatorService(UmletCreatorABC):
def __init__(self): def __init__(self):
UmletCreatorABC.__init__(self) UmletCreatorABC.__init__(self)
def generate_xml(self, classes: list[PythonClass]) -> str: self._padding = 30
xml_cls = ''
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_width = 80
default_height = 80 default_height = 80
next_x = 10 next_x = self._padding
for cls in classes: 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 # save class xml
xml_cls += uml_cls.as_xml() xml_classes += uml_cls.as_xml()
next_x += round(uml_cls.dimension.width, -1) + 10
return f"""\ return f"""\
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<diagram program="umlet" version="14.1.1"> <diagram program="umlet" version="14.1.1">
<zoom_level>10</zoom_level> <zoom_level>10</zoom_level>
{xml_cls} {xml_classes}
{xml_relations}
</diagram> </diagram>
""".replace(' ', '').replace('\t', '') """.replace(' ', '').replace('\t', '')