From 78df3ccbcb88e47079263b03c69007b7596061ff Mon Sep 17 00:00:00 2001 From: Nicolas Delsaux Date: Sun, 17 Oct 2021 17:15:21 +0200 Subject: [PATCH] #46 Support for varous entity types, with or without attributes --- examples/demo-entity-relation.py | 35 +++++++++++++++ pyyed/__init__.py | 60 ++++++++++++++++++++++--- tests/test_pyyed.py | 10 ++--- tests/test_pyyed_entity_relationship.py | 34 ++++++++++++++ 4 files changed, 128 insertions(+), 11 deletions(-) create mode 100644 examples/demo-entity-relation.py create mode 100644 tests/test_pyyed_entity_relationship.py diff --git a/examples/demo-entity-relation.py b/examples/demo-entity-relation.py new file mode 100644 index 0000000..38c60d3 --- /dev/null +++ b/examples/demo-entity-relation.py @@ -0,0 +1,35 @@ +from __future__ import print_function + +import sys, os +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) + +import pyyed + +g = pyyed.Graph() +g.add_node('Person', shape_fill="#EEEEEE", + node_type="GenericNode", + ER={"attributes": ["name", "surname"]}) + +#This is an entity without any attribut, for which only name will be set +g.add_node('Role', shape_fill="#EEEEEE", + node_type="GenericNode", + ER={}) + +g.add_node('Kind', shape_fill="#EEEEEE", + node_type="GenericNode", + ER={'weak':'true'}) + +# g.add_node('ICar', shape_fill="#EEEEEE", +# node_type="UMLClassNode", +# UML={"stereotype": "interface", +# "attributes": "", +# "methods": "getModel()\ngetManufacturer()\ngetPrice()\nsetPrice()"}) + +#g.add_node('Vehicle', shape_fill="#EEEEEE", node_type="UMLClassNode") +#g.add_edge('Car', 'Vehicle', arrowhead="white_delta") +#g.add_edge('Car', 'ICar', arrowhead="white_delta", line_type="dashed") +#g.add_node('This is a note', shape_fill="#EEEEEE", node_type="UMLNoteNode") + +# print(g.get_graph()) + +g.write_graph('demo-entity-relation.graphml') diff --git a/pyyed/__init__.py b/pyyed/__init__.py index 1fbab53..405db87 100644 --- a/pyyed/__init__.py +++ b/pyyed/__init__.py @@ -226,6 +226,7 @@ def __init__(self, node_name, label=None, label_alignment="center", shape="recta shape_fill="#FF0000", transparent="false", border_color="#000000", border_type="line", border_width="1.0", height=False, width=False, x=False, y=False, node_type="ShapeNode", UML=False, + ER=False, custom_properties=None, description="", url=""): self.label = label @@ -234,8 +235,14 @@ def __init__(self, node_name, label=None, label_alignment="center", shape="recta self.node_name = node_name + # Entity node must be created as a GenericNode with a configuration attribute self.node_type = node_type - self.UML = UML + # library defines the used yEd library. Currently, we support UML and Entity Relation + self.library = {} + if UML: + self.library['UML']=UML + elif ER: + self.library['ER']=ER self.parent = None @@ -324,19 +331,49 @@ def convert(self): alignment=self.label_alignment) label.text = self.label - ET.SubElement(shape, "y:Shape", type=self.shape) + subshape = ET.SubElement(shape, "y:Shape", type=self.shape) - if self.UML: + if 'UML' in self.library: UML = ET.SubElement(shape, "y:UML") attributes = ET.SubElement(UML, "y:AttributeLabel", type=self.shape) - attributes.text = self.UML["attributes"] + attributes.text = self.library['UML']["attributes"] methods = ET.SubElement(UML, "y:MethodLabel", type=self.shape) - methods.text = self.UML["methods"] + methods.text = self.library['UML']["methods"] - stereotype = self.UML["stereotype"] if "stereotype" in self.UML else "" + stereotype = self.library['UML']["stereotype"] if "stereotype" in self.library['UML'] else "" UML.set("stereotype", stereotype) + elif 'ER' in self.library: + shape.remove(subshape) + shape.set("configuration", "com.yworks.entityRelationship.big_entity") + label.set("configuration", "com.yworks.entityRelationship.label.name") + if 'attributes' in self.library['ER']: + attributes = ET.SubElement(shape, "y:NodeLabel", fontFamily=self.font_family, + fontSize=self.font_size, + underlinedText=self.underlined_text, + fontStyle=self.font_style, + alignment='left', + configuration="com.yworks.entityRelationship.label.attributes", + modelName='internal', + modelPostion='t') + attributes_param = self.library['ER']['attributes'] + if isinstance(attributes_param, list): + attributes_param = "\n".join(attributes_param) + attributes.text = attributes_param + labelModel = ET.SubElement(attributes, "y:LabelModel") + modelParameter = ET.SubElement(attributes, "y:ModelParameter") + ET.SubElement(labelModel, "y:ErdAttributesNodeLabelModel") + ET.SubElement(modelParameter, "y:ErdAttributesNodeLabelModelParameter") + + label.set('modelName', 'internal') + label.set('modelPosition', 't') + styledProperty = ET.SubElement(shape, "y:StyleProperties") + ET.SubElement(styledProperty, "y:Property", {'class':"java.lang.Boolean", 'name':"y.view.ShadowNodePainter.SHADOW_PAINTING", 'value':"true"}) + weak = 'false' + if 'weak' in self.library['ER'] and self.library['ER']['weak']: + weak = 'true' + ET.SubElement(styledProperty, "y:Property", {'class':"java.lang.Boolean", 'name':"doubleBorder", 'value':weak}) if self.url: url_node = ET.SubElement(node, "data", key="url_node") @@ -565,6 +602,17 @@ def get_graph(self): return ET.tostring(self.graphml, encoding='UTF-8').decode() def add_node(self, node_name, **kwargs): + """ + Adds a node to the graph. + + Parameters + ---------- + node_name : str + name of generated node + kwargs : dict, optional + contains a list of additionnal parameters. + Supported values are all parameters to Node constructor + """ if node_name in self.existing_entities: raise RuntimeWarning("Node %s already exists" % node_name) diff --git a/tests/test_pyyed.py b/tests/test_pyyed.py index 6b303ac..9437704 100644 --- a/tests/test_pyyed.py +++ b/tests/test_pyyed.py @@ -48,9 +48,9 @@ def test_uml_node_properties_are_set(): "attributes": expected_attributes, "methods": expected_methods}) - assert g.nodes["AbstractClass"].UML["stereotype"] == expected_stereotype - assert g.nodes["AbstractClass"].UML["attributes"] == expected_attributes - assert g.nodes["AbstractClass"].UML["methods"] == expected_methods + assert g.nodes["AbstractClass"].library['UML']["stereotype"] == expected_stereotype + assert g.nodes["AbstractClass"].library['UML']["attributes"] == expected_attributes + assert g.nodes["AbstractClass"].library['UML']["methods"] == expected_methods graphml = g.get_graph() assertUmlNode(graphml, expected_stereotype, @@ -67,8 +67,8 @@ def test_uml_stereotype_is_optional(): UML={"attributes": expected_attributes, "methods": expected_methods}) - assert g.nodes["Class"].UML["methods"] == expected_methods - assert g.nodes["Class"].UML["attributes"] == expected_attributes + assert g.nodes["Class"].library['UML']["methods"] == expected_methods + assert g.nodes["Class"].library['UML']["attributes"] == expected_attributes graphml = g.get_graph() assertUmlNode(graphml, "", expected_attributes, expected_methods) diff --git a/tests/test_pyyed_entity_relationship.py b/tests/test_pyyed_entity_relationship.py new file mode 100644 index 0000000..b2d8689 --- /dev/null +++ b/tests/test_pyyed_entity_relationship.py @@ -0,0 +1,34 @@ +import pyyed +import xml.etree.ElementTree as xml + +import pytest + + +def test_er_node_properties_are_set(): + expected_attributes = 'attribute 1\nattribute 2' + + g = pyyed.Graph() + + g.add_node('BigEntity', node_type="GenericNode", + ER={'attributes': expected_attributes}) + + assert g.nodes["BigEntity"].library['ER']["attributes"] == expected_attributes + + graphml = g.get_graph() +# assertERNode(graphml, +# expected_attributes) + + +# def assertERNode(graphml, expected_stereotype, expected_attributes, expected_methods): +# doc = xml.fromstring(graphml) +# nsmap = {'g': 'http://graphml.graphdrawing.org/xmlns', +# 'y': 'http://www.yworks.com/xml/graphml' +# } +# ernode = doc.find( +# 'g:graph/g:node/g:data/y:UMLClassNode/y:UML', namespaces=nsmap) +# attributes = ernode.find('y:AttributeLabel', namespaces=nsmap) +# methods = ernode.find('y:MethodLabel', namespaces=nsmap) +# +# assert ernode.attrib['stereotype'] == expected_stereotype +# assert attributes.text == expected_attributes +# assert methods.text == expected_methods