├── README.md ├── cat-eats-mouse.drawio.plantuml ├── cat-eats-mouse.drawio.xml ├── plantuml_to_drawio_xml.pl └── plantuml_to_drawio_xml.py /README.md: -------------------------------------------------------------------------------- 1 | # Convert PlantUML file to Diagrams.net XML file 2 | 3 | This is a functional script, available in both perl and python, used to demonstrate how to convert a file with PlantUML code into a Diagrams.net (a.k.a Draw.io) XML file from the command line. The PlantUML code and rendered SVG are embedded into the Diagrams.net/Draw.io document. 4 | 5 | Both `plantuml_to_drawio_xml.pl` (perl) and `plantuml_to_drawio_xml.py` (python), written for the purpose of describing the transformation process, perform the same workflow and produce the same output. 6 | 7 | In the Diagrams.net or Draw.io program, the PlantUML diagram can be double-clicked on to see the PlantUML source code. If the PlantUML library is loaded, the PlantUML code can be edited and saved which will update the diagram. 8 | 9 | This script requires java and the PlantUML compiled Jar. It was last tested with OpenJDK 21, PlantUML 1.2024.4, and Draw.io 24.3.1. 10 | 11 | ## Download PlantUML jar library 12 | 13 | https://plantuml.com/download 14 | 15 | ## Create a PlantUML file 16 | 17 | ```bash 18 | @startuml 19 | Cat -> Mouse : eats 20 | @enduml 21 | ``` 22 | 23 | ## Convert a PlantUML file to a Diagrams.net xml file. 24 | 25 | ```bash 26 | system> ./plantuml_to_drawio_xml.pl cat-eats-mouse.plantuml > cat-eats-mouse.drawio.xml 27 | ``` 28 | 29 | ## Import or Load the draw.io xml file 30 | 31 | * in diagrams.net open new 32 | * File > Open from > Device... 33 | * choose the file cat-eats-mouse.drawio.xml 34 | 35 | * in diagrams.net import into existing document 36 | * File > Import from > Device... 37 | * choose the file cat-eats-mouse.drawio.xml 38 | 39 | ## Conversion steps 40 | 41 | Comments in the code describe the steps to convert PlantUML to Diagram.net or Draw.io xml file 42 | 43 | 1. Convert PlantUML plain text data to SVG data using java and the PlantUML jar 44 | 1. Encode the PlantUML SVG data using Base64 45 | 1. Encode the PlantUML text data for XML/HTML/URL format 46 | 1. Obtain the SVG dimensions from he SVG data to add into the Draw.io XML parameters 47 | 1. Obtain the last modified date of the PlantUML file to set as the revision date in the Draw.io XML parameters 48 | 1. Create the Draw.io XML file and output to standard out 49 | 50 | ## Configuration 51 | 52 | There are several properties that can be changed in the plantuml_to_drawio_xml.pl file. Open the file and look at the top. The path to Java and the PlantUML jar can be changed as well as a few Draw.io ID properties. 53 | 54 | ## License 55 | 56 | Apache License 2.0, see [LICENSE](https://www.apache.org/licenses/LICENSE-2.0). 57 | 58 | This program is free software 59 | -------------------------------------------------------------------------------- /cat-eats-mouse.drawio.plantuml: -------------------------------------------------------------------------------- 1 | @startuml 2 | Cat -> Mouse : eats 3 | @enduml 4 | -------------------------------------------------------------------------------- /cat-eats-mouse.drawio.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /plantuml_to_drawio_xml.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use MIME::Base64; 5 | use Time::gmtime; 6 | use File::stat; 7 | # use open qw( :encoding(UTF-8) :std ); # Set UTF-8 as the default encoding 8 | use autodie; 9 | use IPC::Open2; 10 | 11 | BEGIN { 12 | use vars qw($JAVABIN $PLANTUMLJAR $DEBUG); 13 | $JAVABIN="java"; 14 | $PLANTUMLJAR="plantuml-1.2024.4.jar"; 15 | $DEBUG=0; 16 | use vars qw($DIO_AGENT_NAME $DIO_ETAG_ID $DIO_DIAGRAM_ID $DIO_USEROBJECT_ID); 17 | $DIO_AGENT_NAME = "5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36"; 18 | $DIO_ETAG_ID = "ETAG111111111111xx01"; 19 | $DIO_DIAGRAM_ID = "DIAGRAM111111111xx01"; 20 | $DIO_USEROBJECT_ID = "USEROBJECT111111xx01-1"; 21 | } 22 | 23 | sub dprint (@) { 24 | print @_ if $DEBUG >= 1; 25 | } 26 | 27 | my $plantuml_file = shift; 28 | my $cmd_err=0; 29 | unless ( -f "$plantuml_file" ) { 30 | print "Usage: $0 \n"; 31 | $cmd_err++; 32 | } 33 | unless ( -f "$PLANTUMLJAR" ) { 34 | print "The java binary should be in the current path, and PlantUML jar in the current directory.\n"; 35 | $cmd_err++; 36 | } 37 | exit 0 if $cmd_err; 38 | my $i=1; 39 | 40 | # 41 | # Convert PlantUML plain text data to SVG data 42 | # 43 | # Read in PlantUML 44 | my $plantuml_textdata; 45 | my $plantuml_svgdata; 46 | open (TPUML,"<$plantuml_file"); 47 | while () { 48 | $plantuml_textdata .= "$_"; 49 | } 50 | chomp($plantuml_textdata); 51 | dprint ($i++,"PlantUML_Text=[$plantuml_textdata]\n"); 52 | close (TPUML); 53 | # 54 | # Convert PlantUML to SVG using PlanUML Java Library 55 | my ($stdout,$stdin); 56 | my $pid = open2($stdout, $stdin, "$JAVABIN -jar $PLANTUMLJAR -tsvg -pipe"); 57 | print $stdin "$plantuml_textdata\n"; 58 | close($stdin); 59 | while (my $line = <$stdout>) { 60 | $plantuml_svgdata .= "$line"; 61 | #last if "$line" =~ /\<\/svg\>/; 62 | } 63 | close($stdout); 64 | waitpid $pid, 0; 65 | dprint ($i++,"PlantUML_SVG=[$plantuml_svgdata]\n"); 66 | 67 | # 68 | # base64 encode the PlantUML SVG data 69 | # 70 | my $plantuml_svgb64data = encode_base64($plantuml_svgdata, ''); 71 | dprint ($i++,"PlantUML_Base64=[$plantuml_svgb64data]\n"); 72 | 73 | # 74 | # XML/HTML/URL encode the PlantUML text data 75 | # Only 5 characters are needed to be escaped for XML, and include newlines and tabs 76 | # 77 | my $plantuml_escapedtextdata = $plantuml_textdata; 78 | $plantuml_escapedtextdata =~ s/\&/\&/g; 79 | $plantuml_escapedtextdata =~ s//\>/g; 81 | $plantuml_escapedtextdata =~ s/"/\"/g; 82 | $plantuml_escapedtextdata =~ s/'"'"'/\'/g; 83 | $plantuml_escapedtextdata =~ s/\r\n/\\n/g; 84 | $plantuml_escapedtextdata =~ s/\n/\\n/g; 85 | $plantuml_escapedtextdata =~ s/\r/\\n/g; #lost carriage returns 86 | $plantuml_escapedtextdata =~ s/\t/\\t/g; 87 | dprint ($i++,"PlantUML_EscapedText=[$plantuml_escapedtextdata]\n"); 88 | 89 | # 90 | # Obtain the SVG dimensions 91 | # 92 | my ($width,$height) = $plantuml_svgdata =~ /.*width:([\d]+px);height:([\d]+px);.*/; 93 | dprint ($i++,"[width=$width,height=$height]\n"); 94 | 95 | # 96 | # Set the Draw.io revision date to the last modified date of the PlantUML file 97 | # 98 | my $filegmtdate = gmtime(stat($plantuml_file)->mtime); 99 | dprint ($i++,"[gmtime=$filegmtdate]\n"); 100 | my $year=($filegmtdate->year() + 1900); 101 | my $month=sprintf("%02d",($filegmtdate->mon() +1)); 102 | my $mday=sprintf("%02d",$filegmtdate->mday()); 103 | my $hour=sprintf("%02d",$filegmtdate->hour()); 104 | my $min=sprintf("%02d",$filegmtdate->min()); 105 | my $sec=sprintf("%02d",$filegmtdate->sec()); 106 | my $gmt_timestamp = (join("-",($year,$month,$mday)) . "T" . join(":",($hour,$min,$sec)) . ".000Z"); # i.e. 2022-06-17T16:28:54.000Z 107 | dprint ($i++,"[gmt_timestamp=$gmt_timestamp]\n"); 108 | 109 | # 110 | # Create the Draw.io file with imported PlantUML code and SVG 111 | # XML Data Template pulled from a random source file 112 | # 113 | my $drawio_xmldata = (' 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | '); 129 | 130 | # 131 | # Print the Draw.io file to STDOUT 132 | # 133 | print $drawio_xmldata; 134 | 135 | __END__ 136 | -------------------------------------------------------------------------------- /plantuml_to_drawio_xml.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import subprocess 5 | import base64 6 | import xml.etree.ElementTree as ET 7 | import os.path 8 | import re 9 | from time import gmtime 10 | from datetime import datetime 11 | 12 | JAVABIN = "java" 13 | PLANTUMLJAR = "plantuml-1.2024.4.jar" 14 | DEBUG = False 15 | 16 | DIO_AGENT_NAME = "5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36" 17 | DIO_ETAG_ID = "ETAG111111111111xx01" 18 | DIO_DIAGRAM_ID = "DIAGRAM111111111xx01" 19 | DIO_USEROBJECT_ID = "USEROBJECT111111xx01-1" 20 | 21 | def dprint(*args, **kwargs): 22 | if DEBUG: 23 | print(*args, **kwargs) 24 | 25 | def main(): 26 | if len(sys.argv) < 2: 27 | print(f"Usage: {sys.argv[0]} ") 28 | sys.exit(1) 29 | 30 | plantuml_file = sys.argv[1] 31 | 32 | if not os.path.isfile(plantuml_file): 33 | print("Error: PlantUML file not found.") 34 | sys.exit(1) 35 | 36 | if not os.path.isfile(PLANTUMLJAR): 37 | print("Error: PlantUML JAR file not found.") 38 | sys.exit(1) 39 | 40 | # 41 | # Convert PlantUML plain text data to SVG data 42 | # 43 | # Read in PlantUML 44 | with open(plantuml_file, 'r') as f: 45 | plantuml_textdata = f.read().strip() 46 | 47 | dprint("PlantUML_Text=[{}]\n".format(plantuml_textdata)) 48 | 49 | # Convert PlantUML to SVG using PlanUML Java Library 50 | cmd = [JAVABIN, "-jar", PLANTUMLJAR, "-tsvg", "-pipe"] 51 | process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 52 | universal_newlines=True) 53 | 54 | try: 55 | stdout, _ = process.communicate(input=plantuml_textdata, timeout=10) 56 | except subprocess.TimeoutExpired: 57 | process.kill() 58 | print("Error: PlantUML process timed out.") 59 | sys.exit(1) 60 | 61 | plantuml_svgdata = stdout 62 | dprint("PlantUML_SVG=[{}]\n".format(plantuml_svgdata)) 63 | 64 | # 65 | # base64 encode the PlantUML SVG data 66 | # 67 | plantuml_svgb64data = base64.b64encode(plantuml_svgdata.encode()).decode() 68 | dprint("PlantUML_Base64=[{}]\n".format(plantuml_svgb64data)) 69 | 70 | # 71 | # XML/HTML/URL encode the PlantUML text data 72 | # Only 5 characters are needed to be escaped for XML, and include newlines and tabs 73 | # 74 | plantuml_escapedtextdata = ( 75 | plantuml_textdata 76 | .replace('&', '&') 77 | .replace('<', '<') 78 | .replace('>', '>') 79 | .replace('"', '"') 80 | .replace("'", ''') 81 | .replace('\r\n', '\\n') 82 | .replace('\n', '\\n') 83 | .replace('\r', '\\n') 84 | .replace('\t', '\\t') 85 | ) 86 | dprint("PlantUML_EscapedText=[{}]\n".format(plantuml_escapedtextdata)) 87 | 88 | # 89 | # Obtain the SVG dimensions 90 | # 91 | svg_width = '' 92 | svg_height = '' 93 | svg_dimensions_match = re.match(r'.*width:([\d]+)px;height:([\d]+)px;.*', plantuml_svgdata) 94 | if svg_dimensions_match: 95 | svg_width = svg_dimensions_match.group(1) 96 | svg_height = svg_dimensions_match.group(2) 97 | 98 | dprint("[width={},height={}]\n".format(svg_width, svg_height)) 99 | 100 | # 101 | # Set the Draw.io revision date to the last modified date of the PlantUML file 102 | # 103 | file_mtime = os.path.getmtime(plantuml_file) 104 | file_gmtime = gmtime(file_mtime) 105 | gmt_timestamp = datetime.fromtimestamp(file_mtime).strftime('%Y-%m-%dT%H:%M:%S.000Z') 106 | 107 | dprint("[gmtime={}]\n".format(gmt_timestamp)) 108 | 109 | # 110 | # Create the Draw.io file with imported PlantUML code and SVG 111 | # XML Data Template pulled from a random source file 112 | # 113 | # 114 | xml_root = ET.Element('mxfile', { 115 | 'host': 'app.diagrams.net', 116 | 'modified': gmt_timestamp, 117 | 'agent': DIO_AGENT_NAME, 118 | 'etag': DIO_ETAG_ID, 119 | 'version': '20.0.1', 120 | 'type': 'embed' 121 | }) 122 | 123 | # 124 | diagram_elem = ET.SubElement(xml_root, 'diagram', {'id': DIO_DIAGRAM_ID, 'name': 'Page-1'}) 125 | 126 | # 127 | mxGraphModel_elem = ET.SubElement(diagram_elem, 'mxGraphModel', { 128 | 'dx': '1219', 'dy': '1005', 'grid': '1', 'gridSize': '10', 129 | 'guides': '1', 'tooltips': '1', 'connect': '1', 'arrows': '1', 130 | 'fold': '1', 'page': '1', 'pageScale': '1', 'pageWidth': '850', 131 | 'pageHeight': '1100', 'math': '0', 'shadow': '0' 132 | }) 133 | 134 | # 135 | root_elem = ET.SubElement(mxGraphModel_elem, 'root') 136 | 137 | # 138 | mxCell_0_elem = ET.SubElement(root_elem, 'mxCell', { 'id': '0' }) 139 | 140 | # 141 | mxCell_1_elem = ET.SubElement(root_elem, 'mxCell', { 'id': '1', 'parent': '0' }) 142 | 143 | # 144 | userObject_elem = ET.SubElement(root_elem, 'UserObject', { 145 | 'label': '', 146 | 'plantUmlData': '{{\n "data": "{}",\n "format": "svg"\n}}'.format(plantuml_escapedtextdata), 147 | 'id': DIO_USEROBJECT_ID 148 | }) 149 | 150 | # Embed SVG image data 151 | svg_image_data = 'data:image/svg+xml,{}'.format(plantuml_svgb64data) 152 | # 153 | mxCell_elem = ET.SubElement(userObject_elem, 'mxCell', { 154 | 'style': 'shape=image;noLabel=1;verticalAlign=top;aspect=fixed;imageAspect=0;image={}'.format(svg_image_data), 155 | 'parent': '1', 156 | 'vertex': '1' 157 | }) 158 | 159 | # 160 | mxGeometry_elem = ET.SubElement(mxCell_elem, 'mxGeometry', { 161 | 'x': '0', 'y': '0', 'width': svg_width, 'height': svg_height, 'as': 'geometry' 162 | }) 163 | 164 | # 165 | # Print the Draw.io file to STDOUT 166 | # 167 | xml_str = ET.tostring(xml_root, encoding='unicode') 168 | print(xml_str) 169 | 170 | if __name__ == "__main__": 171 | main() 172 | --------------------------------------------------------------------------------