1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Python】Drawioで書いたER図をCSV等で出力するコード

Posted at

今回の記事は、

こちらの記事に触発されて書いたものです。
記事の中で紹介されているコードを参考にしつつ、jsonやtsv形式のファイルを出力できるようにいじってみました。
VScodeの拡張機能であるdrawioについて、詳しくは、次の記事をご参照ください:

1. 作成したdrawio

mydraw.png

ざっくりと主キー、外部キー、そのほかといった分類でフィールドを分けています。

作成した.drawio拡張子のファイルからXMLを読み取る方法

大きく分けて2つ方法があり、

  1. ER図を作成後のXXX.drawioファイルの拡張子.drawioXXX.xmlのように拡張子.xmlに変更する
  2. .drawioをテキストエディタで開きなおす。以下で手順を紹介。

手順1:画面上部のファイル名が表示されている部分で、右クリックを押下。
手順2:以下画像の、「Reopen Editor With...」を選択。(日本語だともう少しわかりやすいかも?)
editorを開きなおす.png

手順3:コマンドパレットのプルダウンから「Text-Editor」を選択。
select_TextEditor.png

すると、XMLを確認することができる。(見づらいのでキャプチャはなしで。百聞は一見に如かずということで。)

2. 処理の概要

XMLの中身を確認してみると、次のようになっていました:

  1. テーブル名について:
    styleの中にshape=table;が含まれているかつvalue属性に値あり(これがテーブル名にあたる)
  2. テーブル名とフィールドの紐づけのための箱?:
    styleの中にshape=table;が含まれているかつvalue属性に値なし(これは行にあたる)
  3. フィールドのプロパティについて:
    styleの中にshape=partialRectangle;が含まれている(これがフィールドのプロパティにあたる)

1.で「Customers」などのテーブル名を取得し、3.でテーブルのフィールドのプロパティ等を取得(主キーや外部キーの分別も可能)、そしてテーブルとフィールドを紐づける中間的なオブジェクトを2.で取得しているといったイメージ。

3. 実際のコード

(自分が)わかりやすくするために、変数を多めに設定してコードを記載:

main.py
import os
import json
import xml.etree.ElementTree as ET
from datetime import datetime

# 元ネタ:https://qiita.com/moto3tt08/items/8ef4a3ffbe3fdce71537

now = datetime.strftime(datetime.now(), "%Y%m%d%H%M%S%f")
dirname = os.path.dirname(__file__)

tables = []
rows = []
fields = []
for file in os.listdir(dirname):
    basename, extension = os.path.splitext(file) # file名と拡張子を分離→タプル
    
    if extension == ".xml":
        root_tree = ET.parse(os.path.join(dirname, file)).getroot() # XMLのルートツリーを取得
        for cell in root_tree.iter("mxCell"):
            table = {}
            row = {}
            field = {}
            id = cell.get("id", "")
            parent = cell.get("parent", "")
            value = cell.get("value", "")
            style = cell.get("style", "")

            if "shape=table;" in style:
                table.update({"id": id, "table": value})
            elif "shape=tableRow;" in style:
                row.update({"id": id, "parent": parent})
            elif "shape=partialRectangle;" in style:
                if "PK" == value:
                    field.update({"id": id, "parent": parent, "PK": value})
                elif "FK" == value:
                    field.update({"id": id, "parent": parent, "FK": value})
                else:
                    field.update({"id": id, "parent": parent, "FIELD": value})
            
            if table:
                tables.append(table)
            if row:
                rows.append(row)
            if field:
                fields.append(field)

header = ["table_name", "field(pk)", "field(fk)", "field(others)"]
for row in rows:
    row_id = row.get("id")
    for field in fields:
        if row_id == field.get("parent"):
            field.pop("parent")
            row.update(field)

records_dict = [] # dict形式でテーブルを整理
records_comma = [] # カンマ区切りでテーブルを整理(複数フィールドがある場合は、パイプ区切り"|")
records_tab = [] # タブ区切りでテーブルを整理(複数フィールドがある場合は、パイプ区切り"|")
for table in tables:
    id = table.get("id")
    table_name = table.get("table")
    field_pk = []
    field_fk = []
    field_others = []
    for row in rows:
        if id == row.get("parent"):
            if row.get("PK",""):
                field_pk.append(row.get("FIELD")) 
            elif row.get("FK",""):
                field_fk.append(row.get("FIELD"))
            else:
                field_others.append(row.get("FIELD"))
    records_dict.append({
        "table_name": table_name,
        "field(pk)": field_pk,
        "field(fk)": field_fk, 
        "field(others)": field_others
    })
    records_comma.append(
        ",".join([table_name, "|".join(field_pk), "|".join(field_fk), "|".join(field_others)])
    )
    records_tab.append(
        "\t".join([table_name, "|".join(field_pk), "|".join(field_fk), "|".join(field_others)])
    )

json_file = f"{now}.json" # jsonファイル名
csv_file = f"{now}.csv" # csvファイル名
tsv_file = f"{now}.tsv" # tsvファイル名
with open(os.path.join(dirname, json_file), "w", encoding="utf-8") as w:
    w.write(json.dumps(records_dict,indent=4, ensure_ascii=False))
with open(os.path.join(dirname, csv_file), "w", encoding="utf-8") as w:
    w.write(",".join(header)+"\n")
    [w.write(record+"\n") for record in records_comma]
with open(os.path.join(dirname, tsv_file), "w", encoding="utf-8") as w:
    w.write("\t".join(header)+"\n")
    [w.write(record+"\n") for record in records_tab]

※VScodeの設定でタブをスペースに置換している場合、Excelに張り付けてもタブ区切りで分割できない。そのため参照のページで設定を変更する必要あり。(参照:https://boonx4m312s.hatenablog.com/entry/2022/06/24/210000)

上記コードを実行すると、json, csv, tsvの3つのファイルが作成されるはずです。以下はサンプルです。:

20230919000000000000.json
[
    {
        "table_name": "Customers",
        "field(pk)": [
            "customer_id int NOT NULL"
        ],
        "field(fk)": [],
        "field(others)": [
            "customer_name char(50) NOT NULL",
            "Row 2",
            "Row 3"
        ]
    },
    {
        "table_name": "Orders",
        "field(pk)": [
            "order_id int NOT NULL"
        ],
        "field(fk)": [
            "customer_id int NOT NULL"
        ],
        "field(others)": [
            "order_date date NOT NULL",
            "Row 3"
        ]
    },
    {
        "table_name": "Shipments",
        "field(pk)": [
            "shipment_id int NOT NULL"
        ],
        "field(fk)": [
            "order_id int NOT NULL"
        ],
        "field(others)": [
            "shipment_date date NOT NULL",
            "Row 3"
        ]
    }
]
20230919000000000000.csv
table_name,field(pk),field(fk),field(others)
Customers,customer_id int NOT NULL,,customer_name char(50) NOT NULL|Row 2|Row 3
Orders,order_id int NOT NULL,customer_id int NOT NULL,order_date date NOT NULL|Row 3
Shipments,shipment_id int NOT NULL,order_id int NOT NULL,shipment_date date NOT NULL|Row 3
20230919000000000000.tsv
table_name	field(pk)	field(fk)	field(others)
Customers	customer_id int NOT NULL		customer_name char(50) NOT NULL|Row 2|Row 3
Orders	order_id int NOT NULL	customer_id int NOT NULL	order_date date NOT NULL|Row 3
Shipments	shipment_id int NOT NULL	order_id int NOT NULL	shipment_date date NOT NULL|Row 3

※複数のフィールドがある場合は、パイプ区切りで表現しています。

4. まとめ

今回はXMLからcsv, tsvおよびjsonファイルを作成するコードを紹介しました。
次回は、csvまたはtsvファイルなどからXMLを作成し、作図ができないか検証をしてみようと思います。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?