diff --git a/src/py_to_uxf/application.py b/src/py_to_uxf/application.py
index ba25583..135d07f 100644
--- a/src/py_to_uxf/application.py
+++ b/src/py_to_uxf/application.py
@@ -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()
diff --git a/src/py_to_uxf_core/abc/umlet_creator_abc.py b/src/py_to_uxf_core/abc/umlet_creator_abc.py
index 18f6e34..c387234 100644
--- a/src/py_to_uxf_core/abc/umlet_creator_abc.py
+++ b/src/py_to_uxf_core/abc/umlet_creator_abc.py
@@ -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
diff --git a/src/py_to_uxf_core/model/dimension.py b/src/py_to_uxf_core/model/dimension.py
index 8016038..781a4ca 100644
--- a/src/py_to_uxf_core/model/dimension.py
+++ b/src/py_to_uxf_core/model/dimension.py
@@ -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})'
diff --git a/src/py_to_uxf_core/model/position.py b/src/py_to_uxf_core/model/position.py
index 78db191..7e8fbf2 100644
--- a/src/py_to_uxf_core/model/position.py
+++ b/src/py_to_uxf_core/model/position.py
@@ -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})'
diff --git a/src/py_to_uxf_core/model/uml_class.py b/src/py_to_uxf_core/model/uml_class.py
index bd72e12..09339f3 100644
--- a/src/py_to_uxf_core/model/uml_class.py
+++ b/src/py_to_uxf_core/model/uml_class.py
@@ -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"""\
+
+ Relation
+
+ {base_point.x}
+ {base_point.y}
+ 0
+ 0
+
+ lt=<<-
+
+ {float(path_position.x)};{float(path_position.y)};{float(path_dimension.width)};{float(path_dimension.height)}
+
+ """.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"""\
UMLClass
@@ -77,9 +115,9 @@ class UMLClass:
{self._cls.name}
--
- {attributes}
+ {self._attributes}
--
- {functions}
+ {self._functions}
diff --git a/src/py_to_uxf_core/service/umlet_creator_service.py b/src/py_to_uxf_core/service/umlet_creator_service.py
index 3b81449..f288a67 100644
--- a/src/py_to_uxf_core/service/umlet_creator_service.py
+++ b/src/py_to_uxf_core/service/umlet_creator_service.py
@@ -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"""\
10
- {xml_cls}
+ {xml_classes}
+ {xml_relations}
""".replace(' ', '').replace('\t', '')