draw.ioのエクスポートファイル(.drawio / .drawio.svg / .drawio.png)の中身を覗いてみた
データ事業本部の横山です。
アーキテクチャ図やシステム構成図を作成する際に、draw.ioのVSCode拡張機能を用いてxxx.drawio.pngのファイル作成をよく行います。
ファイル参照した時や、Wiki等に貼り付けた時に画像として閲覧可能で、かつそのまま拡張機能上で編集が可能なので便利だなあと思っています。
xxx.drawio.png, xxx.drawio.svgといった一見画像ファイルに見える拡張子をもつファイルが、画像として表示できるだけでなく、そのままの形で編集可能なのはなぜなのか、画像ファイルなのにどうやって編集データを保持しているのか気になったので、各フォーマットのファイルの中身を調べてみました。
この記事では、.drawio / .drawio.svg / .drawio.pngの3つのエクスポート形式について、図のデータがどのように格納されているかを確認していきます。
結論
3つのファイル形式はすべて同じ<mxfile>という要素を根本にもつXMLデータを保持しています。違いはそのXMLの格納方式だけです。
| フォーマット | ファイル種別 | 図データの格納方式 | デコード手順 |
|---|---|---|---|
| .drawio | テキスト(XML) | URLエンコード → deflate圧縮 → Base64 | Base64デコード → deflate展開 → URLデコード |
| .drawio.svg | テキスト(XML) | .drawioと同じ(HTMLエスケープで包まれている) | HTMLデコード → Base64デコード → deflate展開 → URLデコード |
| .drawio.png | バイナリ | URLエンコードのみ(tEXtチャンクに格納) | URLデコード |
どの形式からデコードしても同じXMLに到達するため、.drawio.svgや .drawio.pngをdraw.ioで再び開いて編集できるというわけです。各フォーマットの詳細は後述します。
注意: 上記の圧縮・非圧縮の区分は、今回の検証環境(VSCode Draw.io拡張機能)で生成したファイルの結果です。draw.io Web版やデスクトップ版など、ツールやバージョンによって圧縮の有無が異なる可能性がありますが、今回知りたいことの本質ではないので考慮外とします。
draw.ioとは
| 項目 | 内容 |
|---|---|
| 正式名称 | draw.io(diagrams.net) |
| 種類 | オープンソースのダイアグラム作成ツール |
| 利用形態 | Web版/デスクトップ版/ VSCode拡張 |
| 公式サイト | https://www.drawio.com/ |
フローチャートやシステム構成図など、さまざまなダイアグラムを作成できるツールです。
ローカルだけでも完結することができ、AWS固有のリソースやグループ枠といったアイコンをデフォルトで利用可能なためよく利用されている印象です。
前提条件
わたしが動作確認した環境は以下の通りです。
- VSCode + Draw.io Integration拡張機能
- 空のダイアグラム(図形なし)を作成し、Exportで .drawio.svg / .drawio.pngを生成
- XMLファイル:
xxx.drawio - SVGファイル:
xxx.drawio.svg - PNGファイル:
xxx.drawio.png
- XMLファイル:
余談:「何もせず保存」しただけの .drawioは空ファイルになる
余談ですが、検証の下準備で気付いたことを一つ。VSCode Draw.io拡張機能で新規に.drawioを作成して、何も配置せずそのまま保存しただけのファイルは0バイトの空ファイルになります。
念のため別の新規ファイルでも試してみましたが、やはり0バイトでした。どうやら拡張機能は「変更が無いまま保存しても書き込みを行わない」仕様のようです。
メニューから「Export」を選択して.drawioを選択して出力した316バイトのsvgファイルを今回の検証でベースとなる最もミニマムなファイルとして扱うこととします。
ExportとConvertの違い
VSCode Draw.io拡張機能には「Export」と「Convert」の2つの操作があります。違いは出力ファイルの拡張子で、Convertでは.drawio.svg / .drawio.pngのように.drawioが間に挟まった拡張子になるくらいの差です。
.drawioが挟まった拡張子のファイルはVSCodeで開くと拡張機能側の編集エディタが自動で起動する、という利点があります。図形そのものを表す<root>以下の論理的な図データは同一になります。
以降の説明ではとりあえずExportのほうで統一します。
draw.ioの「図データ」とは
まず、すべてのフォーマットに共通するdraw.ioの図データの構造を見ておきましょう。
draw.ioは以下の3層構造のXMLで図の情報を管理しています。
<mxfile> ← ファイル全体の入れ物
<diagram id="..." name="ページ1"> ← 1ページ分のダイアグラム
<mxGraphModel dx="263" dy="249" grid="1" gridSize="10" ...> ← 図の本体
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
</root>
</mxGraphModel>
</diagram>
</mxfile>
| 要素 | 説明 |
|---|---|
<mxfile> |
ファイル全体のルート要素。複数ページをまとめる入れ物 |
<diagram> |
1ページ分のダイアグラム。name属性がページ名 |
<mxGraphModel> |
図の本体。キャンバスの設定(サイズ、グリッド幅など)を持つ |
<mxCell id="0"> |
ルートレイヤー(すべての図形の最上位の親) |
<mxCell id="1" parent="0"> |
デフォルトレイヤー(通常の図形はこの下にぶら下がる) |
今回は空のダイアグラムなので図形はなく、2つのmxCellだけが存在します。図形を追加すると、ここに<mxCell>が増えていくみたいです。
フォーマットごとの違いは<diagram>の中身だけ
重要なポイントとして、<mxfile><diagram>の外側構造は全フォーマットで共通です。違いは<diagram>の中身だけです。
全フォーマット共通:
<mxfile>
<diagram id="..." name="ページ1">
★ここだけがフォーマットによって異なる★
</diagram>
</mxfile>
| フォーマット | <diagram>の中身(<mxGraphModel>) |
|---|---|
| .drawio | ddHBEoIg...(<mxGraphModel>を圧縮 → Base64にした文字列) |
| .drawio.svg | .drawioと同一(圧縮Base64) |
| .drawio.png | <mxGraphModel>...</mxGraphModel>(そのままのXML) |
.drawioと .drawio.svgは<mxGraphModel>を圧縮して格納し、.drawio.pngだけが展開済みのXMLをそのまま格納しています。それぞれ詳しく見ていきましょう。
各フォーマットの詳細
1. .drawio — テキストファイル(XML)
.drawioファイルの実体はただのXMLテキストファイルです。VSCodeではDraw.io拡張機能がインストールされていると自動的にダイアグラムエディタとして開かれますが、エディタのタブを右クリック →「Reopen Editor With...」→「Text Editor」を選べばテキストとして中身を確認できます。
<mxfile>
<diagram id="HvFmt3zLuYUq-mwVFvXP" name="ページ1">
ddHBEoIgEADQr+Gu0FidzfLSyUNnRjZhBl0GabS+Ph0wY6wLszwWlgXC...(長い文字列)...
</diagram>
</mxfile>
補足: 実際のファイルでは改行・インデントなしの1行で格納されています。
<diagram>の中にあるddHBEoIg...という文字列が<mxGraphModel>を圧縮・エンコードしたものです。以下の手順で変換されています。
元の XML → URLエンコード → deflate圧縮 → Base64エンコード = <diagram>の中身
それぞれの変換を簡単に説明します。
| ステップ | やっていること | 例 |
|---|---|---|
| URLエンコード | <や"などの特殊文字を%3C %22のような安全な表記に変換する |
<root> → %3Croot%3E |
| deflate圧縮 | データを小さくする(ZIP圧縮と同じようなもの)。結果はバイナリ | 435バイト → 176バイト |
| Base64エンコード | バイナリデータをA-Z a-z 0-9 + /の文字だけで表す。メールの添付ファイルなどでも使われる方式 |
バイナリ → ddHBEoIg... |
つまり<diagram>に含まれていたよくわからない長い文字列は、下記の手順で変換を行えば中身が読めるはずです。
<diagram>の中身 → Base64デコード → deflate展開 → URLデコード = 元の XML
実際にデコードしてみる
図形を配置していない空の.drawioファイルの<diagram>内の文字列を、ステップごとにデコードした結果を示します。
Step 0 — 元の文字列(<diagram>の中身)
元の文字列はこれです。
ddHBEoIgEADQr+Gu0FidzfLSyUNnRjZhBl0GabS+Ph0wY6wLszwWlgXC8na8WG7kFQVoQhMxEnYilNKMTeMMzwC7o4fGKuEpXaFSLwiYBH0oAX2U6BC1UybGGrsOahcZtxaHOO2OOq5qeAMbqGqut3pTwkmvB7pfvQTVyKVymoX+Wr4kh056yQUOX8QKwnKL6HzUjjno+e2Wd/H7zn9WPxez0LkfG6ZgPXuaRB/Eijc=
ランダムに見える文字列ですがJWTのBearer tokenとかもおんなじ感じだった気がします。
A-Z, a-z, 0-9, +, /, = だけで構成されていますね。
Step 1 — Base64デコード → バイナリデータ → 16進数変換
base64デコードを行って、バイナリのままじゃターミナル上ではよくわかんない出力になってしまうので16進数で表示してみましょう。
echo 'ddHBEoIgEADQr+Gu0FidzfLSyUNnRjZhBl0GabS+Ph0wY6wLszwWlgXC8na8WG7kFQVoQhMxEnYilNKMTeMMzwC7o4fGKuEpXaFSLwiYBH0oAX2U6BC1UybGGrsOahcZtxaHOO2OOq5qeAMbqGqut3pTwkmvB7pfvQTVyKVymoX+Wr4kh056yQUOX8QKwnKL6HzUjjno+e2Wd/H7zn9WPxez0LkfG6ZgPXuaRB/Eijc=' | \
base64 -d | \
xxd
00000000: 75d1 c112 8220 1000 d0af e1ae d058 9dcd u.... .......X..
00000010: f2d2 c943 6746 3661 065d 0669 b4be 3e1d ...CgF6a.].i..>.
00000020: 3063 ac0b b33c 1696 05c2 f276 bc58 6ee4 0c...<.....v.Xn.
00000030: 1505 6842 1331 1276 2294 d28c 4de3 0ccf ..hB.1.v"...M...
00000040: 00bb a387 c62a e129 5da1 522f 0898 047d .....*.)].R/...}
00000050: 2801 7d94 e810 b553 26c6 1abb 0e6a 1719 (.}....S&....j..
00000060: b716 8738 ed8e 3aae 6a78 031b a86a aeb7 ...8..:.jx...j..
00000070: 7a53 c249 af07 ba5f bd04 d5c8 a572 9a85 zS.I..._.....r..
00000080: fe5a be24 874e 7ac9 050e 5fc4 0ac2 728b .Z.$.Nz..._...r.
00000090: e87c d48e 39e8 f9ed 9677 f1fb ce7f 563f .|..9....w....V?
000000a0: 17b3 d0b9 1f1b a660 3d7b 9a44 1fc4 8a37 .......`={.D...7
文字化けのような出力よりはマシですが、人間には読めませんね。
Step 2 — 一気にデコードして元のXMLを取り出す
xxd(16進数変換)ではなく、pythonでBase64デコード → deflate展開 → URLデコードを一気にやってみます。
echo 'ddHBEoIgEADQr+Gu0FidzfLSyUNnRjZhBl0GabS+Ph0wY6wLszwWlgXC8na8WG7kFQVoQhMxEnYilNKMTeMMzwC7o4fGKuEpXaFSLwiYBH0oAX2U6BC1UybGGrsOahcZtxaHOO2OOq5qeAMbqGqut3pTwkmvB7pfvQTVyKVymoX+Wr4kh056yQUOX8QKwnKL6HzUjjno+e2Wd/H7zn9WPxez0LkfG6ZgPXuaRB/Eijc=' | \
base64 -d | \
python3 -c "import zlib,sys,urllib.parse; raw=zlib.decompress(sys.stdin.buffer.read(),-15); print(urllib.parse.unquote(raw.decode()))"
<mxGraphModel dx="263" dy="249" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0"><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel>
中身のxmlが出てきましたね。
これがdraw.ioの図データの本体です。
2. .drawio.svg — テキストファイル(XML)
SVGはテキストエディタで開けるXMLベースの画像フォーマットです。エクスポートした.drawio.svgの中身を見てみましょう。
元ファイル自体がアイコン等を配置していないためSVGとしての構成要素はほぼなく、content属性に押し込まれているみたいですね。
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
style="background: transparent; background-color: transparent;"
width="1px" height="1px" viewBox="-0.5 -0.5 1 1"
content="<mxfile><diagram ...>ddHBEoIg...</diagram></mxfile>">
<defs/><g/>
</svg>
| 部分 | 説明 |
|---|---|
<svg>タグ |
1x1ピクセルのSVG画像(空なので最小サイズ) |
<defs/>, <g/> |
図形の描画定義とグループ。空のダイアグラムなので中身は空 |
content属性 |
ここにdraw.ioのソースデータが丸ごと埋め込まれている |
content属性には.drawioファイルの中身(<mxfile>...</mxfile>)がHTMLエンティティエスケープされて格納されています。
| 元の文字 | エスケープ後 | 理由 |
|---|---|---|
< |
< |
XMLの属性値の中に<をそのまま書くと壊れるため |
> |
> |
同上 |
" |
" |
属性値を囲む"と衝突するため |
エスケープを元に戻すと.drawioファイルと同じ内容が得られるはずです。つまり<diagram>の中には同じ圧縮済みBase64文字列が入っているということになります。
実際にデコードしてみる
SVGファイルからcontent属性を取り出して、HTMLエンティティをデコードしてみましょう。PythonのXMLパーサーを使うと、属性値の取得時に自動でHTMLエンティティがデコードされます。
python3 -c "
import xml.etree.ElementTree as ET
svg = ET.parse('target_file.svg').getroot()
content = svg.get('content')
print(content)
"
<mxfile><diagram id="HvFmt3zLuYUq-mwVFvXP" name="ページ1">ddHBEoIgEADQr+Gu0FidzfLSyUNnRjZhBl0GabS+Ph0wY6wLszwWlgXC8na8WG7kFQVoQhMxEnYilNKMTeMMzwC7o4fGKuEpXaFSLwiYBH0oAX2U6BC1UybGGrsOahcZtxaHOO2OOq5qeAMbqGqut3pTwkmvB7pfvQTVyKVymoX+Wr4kh056yQUOX8QKwnKL6HzUjjno+e2Wd/H7zn9WPxez0LkfG6ZgPXuaRB/Eijc=</diagram></mxfile>
<mxfile><diagram>...Base64文字列...</diagram></mxfile>という構造が出てきました。.drawioファイルの中身と同じものが出てきました。
つまり.drawio.svgの場合も、content属性の中身をHTMLデコードした後、.drawioと同じ手順(Base64デコード → deflate展開 → URLデコード)で<mxGraphModel>XMLに到達できます。
content属性の取得 → HTMLデコード → Base64デコード → deflate展開 → URLデコード = 元の XML
3. .drawio.png — バイナリファイル
PNGはバイナリファイルなので、テキストエディタでは読めません。ただしPNGの仕様にはテキストデータを埋め込む仕組みが用意されており、draw.ioはこれを利用しているみたいです。
PNGのテキスト埋め込みチャンク — tEXtとzTXt
PNGファイルは「チャンク」と呼ばれるブロックの並びでできています。各チャンクには種類を示す4文字の名前(タイプ)があり、役割ごとに分かれています。
PNGの仕様にはテキストを埋め込むためのチャンクが2種類あります。
| チャンクタイプ | 説明 |
|---|---|
tEXt |
テキストをそのまま格納する(非圧縮) |
zTXt |
テキストを zlib圧縮して格納する(圧縮) |
draw.ioの公式ブログでは「zTXtセクションにXMLコードを含める」と説明されています。しかし今回VSCode Draw.io拡張機能で生成したファイルでは**tEXt(非圧縮)**が使われていました。
実際のチャンク構成
今回のファイルのチャンク構成を見てみましょう。Pythonコードで確認しましたが、本記事では割愛します。
[PNGシグネチャ] ← このファイルはPNGファイルです、という目印
[IHDR チャンク] ← 画像の基本情報(幅・高さ・色深度など)
[tEXt チャンク] ← 非圧縮テキスト ★ここに draw.io データ
[IDAT チャンク] ← 実際のピクセルデータ(1x1 の RGBA 画像)
[IDAT チャンク] ← ピクセルデータの続き
[IEND チャンク] ← ファイル終端マーカー
tEXtチャンクの中身
tEXtチャンクは「キーワード + テキスト」の組み合わせでメタデータを格納します。draw.ioは以下のように使っています。
| フィールド | 値 |
|---|---|
| キーワード | mxfile |
| テキスト | draw.ioの図データ(URLエンコード済みXML) |
tEXtは非圧縮チャンクなので、XMLは圧縮されていない展開済みの状態で、<mxfile>から始まる完全なXMLがURLエンコードされて入っています。
tEXtチャンクのテキスト → URLデコード = 展開済みの XML
URLデコードすると以下が得られます。
<mxfile>
<diagram id="HvFmt3zLuYUq-mwVFvXP" name="ページ1">
<mxGraphModel dx="263" dy="249" ...>
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
</root>
</mxGraphModel>
</diagram>
</mxfile>
ここで一旦整理すると.drawio / .drawio.svgと.drawio.pngで<diagram>内の形式が異なっているようです。
.drawioの<diagram>内 →ddHBEoIg...(圧縮Base64。<mxGraphModel>で始まらない).drawio.pngの<diagram>内 →<mxGraphModel dx="263" ...(展開済みXML。<mxGraphModel>で始まる)
先述のdraw.ioのブログ内で
zTXtセクションにXMLコードを含めるの通りの実装であったら同一になっていると思われます。
ただ、今回の検証ではtEXtが利用され.drawio.pngだけ展開済みでした。
フォーマット間の比較
エンコード方式の違い
| フォーマット | ファイル種別 | <diagram>内の図データ |
デコード手順 | 理由 |
|---|---|---|---|---|
| .drawio | テキスト(XML) | 圧縮済み(URLエンコード → deflate → Base64) | Base64 → deflate展開 → URLデコード | XML内にテキストとして格納するためBase64化が必要 |
| .drawio.svg | テキスト(XML) | 圧縮済み(.drawioと同じ)※HTMLエスケープで包まれている | HTMLデコード → Base64 → deflate展開 → URLデコード | 同上(HTML属性値内に格納) |
| .drawio.png | バイナリ | 展開済み(URLエンコードのみ) | URLデコード | tEXtチャンク(非圧縮テキスト)を使用しているため |
補足: draw.ioの公式ブログでは.drawio.pngに
zTXt(圧縮テキスト)チャンクを使うと説明されています。zTXtが使われる場合は格納形式が異なる可能性があります。今回の調査はVSCode Draw.io拡張機能で生成したファイルの結果です。
全フォーマットが同じ「図データ」に帰着する
エンコード方式は異なり、<diagram id>や<mxGraphModel dx>といったメタデータもExport / Convertで揺れる部分がありますが、最終的にデコードした<root>以下(図形そのもの)の論理構造はすべて同一です。つまりどのフォーマットからでもdraw.ioの図データは共通であり、完全に復元できるってことですね。
だから、.drawio.svgや.drawio.pngをdraw.ioで再び開いて編集できたんですね。
画像にデータを埋め込む理由
draw.ioが画像に編集データを埋め込んでいる理由は、大きく3つかなと思います。
- 画像としてそのまま使える … SVG/PNGなのでブラウザやMarkdownで表示可能
- 編集データも失われない … 画像の中にdraw.ioのソースが隠されているので、いつでも再編集できる
- 1ファイルで完結する … 「画像ファイル+元データファイル」のように2つ管理する必要がない
画像ファイルを扱うこと自体が少ないので一般的な方法なのかはわかりませんが、便利に使っていきたいですね。
さいごに
draw.ioのエクスポートファイル3形式(.drawio / .drawio.svg / .drawio.png)の内部構造を調べてみました。
3形式とも同じ<mxGraphModel>XMLを保持していて、違いは格納方式だけでした。
「画像でありながら再編集できる」という仕組みは、PNGのテキストチャンクやSVGの属性値といった各フォーマットの既存の仕様を組み合わせて実現されていたんですね。
今回は空のダイアグラムで最小構成を調べましたが、図形を含む場合の<mxCell>の構造や、draw.io Web版でのzTXtチャンクの挙動なども気になるので、機会があれば調べてみたいと思います。








