├── 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;
80 | $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 |
--------------------------------------------------------------------------------