mirror of
ssh://git.janware.com/srv/git/janware/proj/jw-python
synced 2026-01-15 18:03:31 +01:00
191 lines
7.6 KiB
Python
191 lines
7.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from collections.abc import Callable
|
|
|
|
import xml.etree.ElementTree as ET
|
|
|
|
from jwutils.log import *
|
|
|
|
class MapAttr2Shape: # export
|
|
|
|
def __init__(self, mappings: dict[str, str|Callable[[dict[str, str]], str]]|None=None) -> None:
|
|
self.__mappings = mappings if mappings is not None else {}
|
|
self.__shape_node_key = 'd25'
|
|
self.__ns_gml = "http://graphml.graphdrawing.org/xmlns"
|
|
self.__ns = {
|
|
# -- Standard GraphML
|
|
"": self.__ns_gml,
|
|
"xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
|
"xsi:schemaLocation": "http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd",
|
|
|
|
# -- YWorks GraphML
|
|
"java": "http://www.yworks.com/xml/yfiles-common/1.0/java",
|
|
"sys": "http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0",
|
|
"x": "http://www.yworks.com/xml/yfiles-common/markup/2.0",
|
|
"y": "http://www.yworks.com/xml/graphml",
|
|
"yed": "http://www.yworks.com/xml/yed/3",
|
|
}
|
|
# https://stackoverflow.com/questions/4997848/
|
|
for name, url in self.__ns.items():
|
|
ET.register_namespace(name, url)
|
|
|
|
def __keys(self, root) -> dict[str, str]:
|
|
ret: dict[str, str] = {}
|
|
for el in root.findall('key', self.__ns):
|
|
attr_name = el.get('attr.name')
|
|
id = el.get('id')
|
|
if attr_name is not None:
|
|
ret[attr_name] = id
|
|
return ret
|
|
|
|
def __value(self, node, key):
|
|
data = node.find(f'data[@key="{key}"]', self.__ns)
|
|
if data is None:
|
|
return None
|
|
return data.text
|
|
|
|
def __attribs(self, node, keys) -> dict[str, str]:
|
|
ret: dict[str, str] = {}
|
|
for name, key in keys.items():
|
|
val = self.__value(node, key)
|
|
if val is None:
|
|
continue
|
|
ret[name] = val
|
|
return ret
|
|
|
|
def __add_key_nodegraphics(self, root):
|
|
# <graphml><key for="node" id="d22" yfiles.type="nodegraphics"/></graphml>
|
|
el = ET.Element('key')
|
|
for attr, val in {
|
|
'id': self.__shape_node_key,
|
|
'for': 'node',
|
|
'yfiles.type':'nodegraphics'
|
|
}.items():
|
|
el.set(attr, val)
|
|
root.append(el)
|
|
|
|
def __massage_node(self, node, keys: dict[str, str]) -> None:
|
|
|
|
def __add(parent, d: dict[str, Any]):
|
|
for tag, content in d.items():
|
|
if tag.find('y:') != -1:
|
|
ns, tag = tag.split(':')
|
|
tag = '{' + self.__ns[ns] + '}' + tag
|
|
attrib = content.get('a') or {}
|
|
el = ET.Element(tag, attrib=attrib)
|
|
text = content.get('t')
|
|
if text is not None:
|
|
el.text = text
|
|
parent.append(el)
|
|
children = content.get('c')
|
|
if children is not None:
|
|
__add(el, children)
|
|
|
|
default_values = {
|
|
'color': '#FFCC00',
|
|
'text': ''
|
|
}
|
|
values = {}
|
|
|
|
for key, default in default_values.items():
|
|
mapping = self.__mappings.get(key)
|
|
if mapping is None:
|
|
values[key] = default
|
|
continue
|
|
try:
|
|
if isinstance(mapping, str):
|
|
values[key] = mapping
|
|
continue
|
|
mapped = mapping(self.__attribs(node, keys))
|
|
values[key] = mapped or default
|
|
except:
|
|
pass
|
|
|
|
color = values['color']
|
|
text = values['text']
|
|
has_text = 'true' if text else 'false'
|
|
|
|
width_text = round(len(text) * 5.75, 5) if text else 0
|
|
width_box = width_text + 10 if text else 30
|
|
|
|
shape = {
|
|
'data': {
|
|
'a': {'key': self.__shape_node_key},
|
|
'c': {
|
|
'y:ShapeNode': {
|
|
'a': {},
|
|
'c': {
|
|
'y:Geometry': {'a': {'height': '30.0', 'width': str(width_box), 'x': str(-(width_box / 2)), 'y':' -15.0'}},
|
|
'y:Fill': {'a': {'color': color, 'transparent': 'false'}},
|
|
'y:BorderStyle': {'a': {'color': '#000000', 'raised': 'false', 'type': 'line', 'width': '1.0'}},
|
|
'y:NodeLabel': {
|
|
'a': {
|
|
'alignment': 'center',
|
|
'autoSizePolicy': 'content',
|
|
'fontFamily': 'Dialog',
|
|
'fontSize': '12',
|
|
'fontStyle': 'plain',
|
|
'hasBackgroundColor': 'false',
|
|
'hasLineColor': 'false',
|
|
'hasText': has_text,
|
|
'height': '18',
|
|
'horizontalTextPosition': 'center',
|
|
'iconTextGap': '4',
|
|
'modelName': 'custom',
|
|
'textColor': '#000000',
|
|
'verticalTextPosition': 'bottom',
|
|
'visible': 'true',
|
|
'width': str(width_text),
|
|
'x': '13.0',
|
|
'y': '13.0',
|
|
},
|
|
'c': {
|
|
'y:LabelModel': {
|
|
'c': {
|
|
'y:SmartNodeLabelModel': {'a': {'distance': '4.0'}}
|
|
},
|
|
},
|
|
'y:ModelParameter': {
|
|
'c': {
|
|
'y:SmartNodeLabelModelParameter': {
|
|
'a': {
|
|
'labelRatioX':'0.0',
|
|
'labelRatioY': '0.0',
|
|
'nodeRatioX': '0.0',
|
|
'nodeRatioY': '0.0',
|
|
'offsetX': '0.0',
|
|
'offsetY': '0.0',
|
|
'upX': '0.0',
|
|
'upY': '-1.0',
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
't': text
|
|
},
|
|
'y:Shape': {'a': {'type': 'rectangle'}}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
__add(node, shape)
|
|
|
|
def __massage_nodes(self, root) -> None:
|
|
keys = self.__keys(root)
|
|
graph = root.find(f'graph', self.__ns)
|
|
for node in graph:
|
|
self.__massage_node(node, keys)
|
|
|
|
def run(self, path_in: str, path_out: str) -> None:
|
|
parser = ET.XMLParser(encoding="utf-8")
|
|
tree = ET.parse(path_in, parser=parser)
|
|
root = tree.getroot()
|
|
|
|
self.__add_key_nodegraphics(root)
|
|
self.__massage_nodes(root)
|
|
|
|
ET.indent(tree, space=' ', level=0)
|
|
tree.write(path_out, xml_declaration=True, encoding='utf-8')
|