draw.ioの全ページを一括変換する

# はじめに
draw.ioで作成した図の全ページを一括で画像ファイルに変換する方法をまとめました。

# 実行環境

* Windows 10 Pro
  * バージョン22H2
* drawio-desktop
  * v 22.1.2
* Python
  * 3.12.0 64bit

# 実装

## ディレクトリ構成

drawioフォルダ内にある`.drawio`ファイルからimageフォルダに`.svg`を作成します。
出力されるファイル名は`{file_name}-{page_name}.svg`になります

```
./
│  make.py
├─drawio # 入力
│      sample1.drawio
│      sample2.drawio
└─image # 出力
        sample1-page-1.svg
        sample1-page-2.svg
        sample2-page 1.svg
        sample2-page 2.svg

```

## コード

```python:make.py
from pathlib import Path
import subprocess
import xml.etree.ElementTree as et

DRAWIO_PATH = '"C:\Program Files\draw.io\draw.io.exe"'
ROOT_DIR = Path(__file__).resolve().parent
INPUT_DIR = ROOT_DIR / "drawio"
OUTPUT_DIR = ROOT_DIR / "image"

def generate_image(input_path : Path):

    try:
        root = et.parse(str(input_path)).getroot()
    except et.ParseError:
        print(f"Error : Failed to parse XML file {input_path}")
        return

    # 1ページの場合pagesが存在しないのでデフォルトを1とする。
    pages = int(root.get("pages", 1))

    for page in range(pages):
        page_name = root[page].get("name")
        output_file_name = f"{input_path.stem}-{page_name}.svg"
        output_path = OUTPUT_DIR / output_file_name

        command = f'{DRAWIO_PATH} "{input_path}" -x -f svg -p {page} -o "{output_path}"'

        try:
            subprocess.run(command, check=True)
        except subprocess.CalledProcessError:
            print(f'Error : Failed to execute "{command}"')

if __name__ == "__main__":

    OUTPUT_DIR.mkdir(exist_ok=True)

    for file_path in INPUT_DIR.glob("*.drawio"):
        generate_image(file_path)
```

<details><summary>旧コード</summary>

```python:make.py
from pathlib import Path
import xml.etree.ElementTree as et
import subprocess


def get_output_path(file_path, page_name, file_type):
    stem = '-'.join([str(file_path.stem), page_name])
    suffix = '.' + file_type
    return file_path.with_name(stem).with_suffix(suffix)


def generate_image(file_path):
    DRAWIO = '"C:\Program Files\draw.io\draw.io.exe"'
    FILE_TYPE = 'svg'

    root = et.parse(file_path).getroot()

    for i in range(int(root.get('pages'))):
        page_name = root[i].get('name')
        # option
        f = ' '.join(['-f', FILE_TYPE])
        p = ' '.join(['-p', str(i)])
        o = ' '.join(['-o', '"' + str(get_output_path(file_path, page_name, FILE_TYPE)) + '"'])
        # run
        command = ' '.join([DRAWIO, '"' + str(file_path) + '"', '-x', f, p, o])
        subprocess.run(command)


if __name__ == '__main__':
    root_dir = Path.cwd()
    for i in root_dir.glob('image\\*.drawio'):
        generate_image(i.relative_to(root_dir))

```
</details>


# 解説

## `.drawio`の読み出し

以下のように`.drawio`ファイル内にはページ数`pages`と各ページの名前`name`の情報があります。
これを`xml.etree.ElementTree`で読み出してファイル名を生成しています。

```xml:sample1.drawio
<mxfile modified="..." ... pages="3">
	<diagram name="page1" id="...">
	    ...
	</diagram>
	<diagram name="page2" id="...">
        ...
    </diagram>
	<diagram name="page3" id="...">
        ...
    </diagram>
</mxfile>
```

python上ではファイル毎に`pages`の数だけ生成処理を行います。

```python:
    try:
        root = et.parse(str(input_path)).getroot()
    except et.ParseError:
        print(f"Error : Failed to parse XML file {input_path}")

    # 1ページの場合pagesが存在しないのでデフォルトを1とする。
    pages = int(root.get("pages", 1))

    for page in range(pages):
        page_name = root[page].get("name")
```

## `.drawio`ファイルの各ページの生成方法

drawioをコマンドライン実行する方法は[draw.io をコマンドラインで実行して、画像ファイルをエクスポートする](https://qiita.com/u4da3/items/646cc46bff68b380cbfa)が参考になります。

`-p, --page-index <pageIndex>`で指定したページを変換することができます。

`-a, --all-pages`はpdf生成のみ対応しているため、画像生成時は使用できないので注意しましょう。

例えば1ページ目を生成する時は以下のようになります。

```cmd:コマンド
"C:\Program Files\draw.io\draw.io.exe" "image/sample1.drawio" -x -f svg -p 0 -o "image/sample1-page1.svg"
```

## その他

ファイル名やページ名に空白が含まれる可能性があるため、pathは`""`で囲うようにしています。

```python:
        command = f'{DRAWIO_PATH} "{input_path}" -x -f svg -p {page} -o "{output_path}"'
```

`""`をつけない場合、以下の例では`sample2-page 1.svg`ではなく`sample2-page`というファイルが生成されてしまいます。

```cmd:コマンド
"C:\Program Files\draw.io\draw.io.exe drawio/sample2.drawio -x -f svg -p 0 -o image/sample2-page 1.svg
```

# 参考
[drawio-cli](https://j2r2b.github.io/2019/08/06/drawio-cli.html)
[draw.io をコマンドラインで実行して、画像ファイルをエクスポートする](https://qiita.com/u4da3/items/646cc46bff68b380cbfa)
[draw.ioの全てのタブを一括で画像出力する](https://qiita.com/icchi_h/items/5e0aaccb3ea3e2e9ee7b)

# 更新履歴

* 2023/12/05
    * 機能変更
        * 入力と出力のフォルダを分離するように変更
        * エラー処理を追加
    * バグ修正
        * drawioファイル内に1ページしかない場合にエラーになる問題を修正
    * その他
        * 実行環境を更新
        * コード全体を整理