|
|
@@ -4,6 +4,7 @@ Functions for converting in-memory drawio data to XML.
|
|
|
|
|
|
import xml.etree.ElementTree as ET
|
|
|
from drawio2py.abstract_syntax import *
|
|
|
+from drawio2py.util import generate_empty_root_and_layer
|
|
|
|
|
|
|
|
|
# generates XML tree and serializes it to a file (or any writeable object)
|
|
|
@@ -114,8 +115,210 @@ def generate_diagram(page: Page) -> ET.Element:
|
|
|
write_cell(page.root)
|
|
|
return dia
|
|
|
|
|
|
+
|
|
|
+def generate_library(lib : Dict[str, Cell], file_object : str) -> None:
|
|
|
+ '''
|
|
|
+ Input:
|
|
|
+ - lib : Dict[str, Cell] \n
|
|
|
+ parsed library for drawio.
|
|
|
+
|
|
|
+ - file_object : str \n
|
|
|
+ saving file. Must end with '.drawio'.
|
|
|
+
|
|
|
+ Output:
|
|
|
+ None
|
|
|
+
|
|
|
+ Create a drawio lib file from a parsed library.
|
|
|
+ '''
|
|
|
+ # Verify if `path` end with ".drawio"
|
|
|
+ if not(file_object.endswith(".drawio") or file_object.endswith(".xml")):
|
|
|
+ raise Exception("The given path is not a correct file name for a drawio file.\nIt must end with '.drawio' or '.xml'.")
|
|
|
+
|
|
|
+ fileContent = "<mxlibrary>[\n"
|
|
|
+
|
|
|
+ keys = lib.keys()
|
|
|
+ nbKeys = len(keys)
|
|
|
+
|
|
|
+ def generate_style(style: Style) -> str:
|
|
|
+ res = ""
|
|
|
+ for k, v in style.data.items():
|
|
|
+ res += "%s=%s;" % (str(k), str(v))
|
|
|
+ return res
|
|
|
+
|
|
|
+ for idx, key in enumerate(keys):
|
|
|
+
|
|
|
+ fileContent += ' {\n'
|
|
|
+
|
|
|
+ # "xml"
|
|
|
+ mxgm = ET.Element("mxGraphModel")
|
|
|
+ root = ET.SubElement(mxgm, "root")
|
|
|
+
|
|
|
+ lib.get(key).properties["label"] = lib.get(key).properties.get("label", '').replace('<', '<')
|
|
|
+ lib.get(key).properties["label"] = lib.get(key).properties.get("label", '').replace('>', '>')
|
|
|
+ lib.get(key).properties["label"] = lib.get(key).properties.get("label", '').replace('"', '"')
|
|
|
+
|
|
|
+ empty_root = generate_empty_root_and_layer()
|
|
|
+ empty_root.children[0].children.append(lib.get(key))
|
|
|
+
|
|
|
+
|
|
|
+ def write_cell(cell):
|
|
|
+ attrs = {}
|
|
|
+ if not cell.properties:
|
|
|
+ attrs["id"] = cell.id
|
|
|
+ if cell.value:
|
|
|
+ attrs["value"] = cell.value
|
|
|
+ if cell.style:
|
|
|
+ attrs["style"] = generate_style(cell.style)
|
|
|
+ if cell.parent:
|
|
|
+ attrs["parent"] = cell.parent.id
|
|
|
+ if isinstance(cell, Edge):
|
|
|
+ attrs["edge"] = "1"
|
|
|
+ if cell.source:
|
|
|
+ attrs["source"] = cell.source.id
|
|
|
+ if cell.target:
|
|
|
+ attrs["target"] = cell.target.id
|
|
|
+
|
|
|
+ if cell.properties:
|
|
|
+ # Wrap in <object> if there are properties
|
|
|
+ properties_with_id = cell.properties.copy()
|
|
|
+ properties_with_id['id'] = cell.id # <object> gets the ID, not the <mxCell> (WTF drawio!)
|
|
|
+ par = ET.SubElement(root, "object", properties_with_id)
|
|
|
+ else:
|
|
|
+ par = root
|
|
|
+
|
|
|
+ # Create the actual <mxCell>
|
|
|
+ c = ET.SubElement(par, "mxCell", attrs, **cell.attributes)
|
|
|
+
|
|
|
+ def optional_attributes(dict):
|
|
|
+ attrs = {}
|
|
|
+ for key, val in dict.items():
|
|
|
+ if val != None:
|
|
|
+ attrs[key] = str(val)
|
|
|
+ return attrs
|
|
|
+
|
|
|
+ def write_point(parent_xml, point: Point, as_str: Optional[str] = None):
|
|
|
+ attrs = optional_attributes({
|
|
|
+ "x": point.x,
|
|
|
+ "y": point.y,
|
|
|
+ "as": as_str,
|
|
|
+ })
|
|
|
+ return ET.SubElement(parent_xml, "mxPoint", attrs)
|
|
|
+
|
|
|
+ def write_geometry(parent_xml, geometry):
|
|
|
+ return ET.SubElement(parent_xml, "mxGeometry", optional_attributes({
|
|
|
+ "x": geometry.x,
|
|
|
+ "y": geometry.y,
|
|
|
+ "width": geometry.width,
|
|
|
+ "height": geometry.height,
|
|
|
+ "relative": "1" if geometry.relative else None,
|
|
|
+ "as": "geometry",
|
|
|
+ }))
|
|
|
+
|
|
|
+ # Geometry
|
|
|
+ if isinstance(cell, Vertex) or isinstance(cell, Edge):
|
|
|
+ g = write_geometry(c, cell.geometry)
|
|
|
+ if cell.geometry.offset != None:
|
|
|
+ write_point(g, cell.geometry.offset, "offset")
|
|
|
+ if isinstance(cell, Edge):
|
|
|
+ if len(cell.geometry.points) > 0:
|
|
|
+ a = ET.SubElement(g, "Array", {"as": "points"})
|
|
|
+ for p in cell.geometry.points:
|
|
|
+ write_point(a, p)
|
|
|
+ if cell.geometry.source_point is not None:
|
|
|
+ write_point(g, cell.geometry.source_point, "sourcePoint")
|
|
|
+ if cell.geometry.target_point is not None:
|
|
|
+ write_point(g, cell.geometry.target_point, "targetPoint")
|
|
|
+
|
|
|
+ for child in cell.children:
|
|
|
+ write_cell(child)
|
|
|
+
|
|
|
+ write_cell(empty_root)
|
|
|
+
|
|
|
+ xml = ET.tostring(mxgm, encoding="unicode")
|
|
|
+ xml = xml.replace('<', '<')
|
|
|
+ xml = xml.replace('>', '>')
|
|
|
+ xml = xml.replace('"', '\\"')
|
|
|
+ fileContent += f' "xml": "{xml}",\n'
|
|
|
+
|
|
|
+ # "w" & "h"
|
|
|
+ geometry = lib.get(key).geometry
|
|
|
+ geometry_class = type(geometry).__name__
|
|
|
+ if geometry_class == "VertexGeometry":
|
|
|
+ try:
|
|
|
+ w = int(geometry.width)
|
|
|
+ except ValueError:
|
|
|
+ try:
|
|
|
+ w = float(geometry.width)
|
|
|
+ except:
|
|
|
+ raise Exception("Invalid width value.")
|
|
|
+ except:
|
|
|
+ raise Exception("Invalid width value.")
|
|
|
+
|
|
|
+ if geometry.x != None:
|
|
|
+ try:
|
|
|
+ x = int(geometry.x)
|
|
|
+ except ValueError:
|
|
|
+ try:
|
|
|
+ x = float(geometry.x)
|
|
|
+ except:
|
|
|
+ raise Exception("Invalid x value.")
|
|
|
+ except:
|
|
|
+ raise Exception("Invalid x value.")
|
|
|
+ w += abs(x)
|
|
|
+
|
|
|
+ try:
|
|
|
+ h = int(geometry.height)
|
|
|
+ except ValueError:
|
|
|
+ try:
|
|
|
+ h = float(geometry.height)
|
|
|
+ except:
|
|
|
+ raise Exception("Invalid height value.")
|
|
|
+ except:
|
|
|
+ raise Exception("Invalid height value.")
|
|
|
+
|
|
|
+ if geometry.y != None:
|
|
|
+ try:
|
|
|
+ y = int(geometry.y)
|
|
|
+ except ValueError:
|
|
|
+ try:
|
|
|
+ y = float(geometry.y)
|
|
|
+ except:
|
|
|
+ raise Exception("Invalid y value.")
|
|
|
+ except:
|
|
|
+ raise Exception("Invalid y value.")
|
|
|
+ h += abs(y)
|
|
|
+
|
|
|
+ if geometry_class == "EdgeGeometry":
|
|
|
+ w = geometry.target_point.x
|
|
|
+ h = geometry.source_point.y
|
|
|
+
|
|
|
+ fileContent += f' "w": {w},\n "h": {h},\n'
|
|
|
+
|
|
|
+ # "aspect"
|
|
|
+ aspect = "fixed"
|
|
|
+ fileContent += f' "aspect": "{aspect}",\n'
|
|
|
+
|
|
|
+ # "title"
|
|
|
+ title = key
|
|
|
+ fileContent += f' "title": "{title}"\n'
|
|
|
+
|
|
|
+ fileContent += ' }'
|
|
|
+
|
|
|
+ if idx < nbKeys - 1:
|
|
|
+ fileContent += ',\n'
|
|
|
+
|
|
|
+ fileContent += "\n]</mxlibrary>"
|
|
|
+
|
|
|
+ # Saved it as a file at `path`
|
|
|
+ with open(file_object, "w") as file:
|
|
|
+ file.write(fileContent)
|
|
|
+
|
|
|
if __name__ == '__main__':
|
|
|
from drawio2py.parser import Parser
|
|
|
+ from drawio2py.shapelib import parse_library
|
|
|
|
|
|
dfile = Parser.parse("../test.drawio")
|
|
|
generate(dfile, "../test2.drawio")
|
|
|
+
|
|
|
+ #d_lib_file = parse_library("../dlib/test.xml")
|
|
|
+ #generate_library(d_lib_file, "../dlib/test2.drawio")
|