PlantUMLからmermeidへの変換器を作ってみた

## モチベーション
最近GitHubが対応したことで一気に熱を帯びているmermeidだが、
PlantUMLからmermeidへの変換器が意外と落ちていなかった。
そこで、今回はPlantUMLからmermeidへの変換器を自作してみることにした。
（こちらの内容は[自分のブログ](https://prostep.link/articles/txfflju6_q)でも公開していますのでぜひご覧下さい。変換器はこちらでリリース予定です。）

## 言葉の説明

### PlantUML
コードベースで作画ができるツール。古くから使われていて慣れ親しんでいる人も多いはず。
ER図、シーケンス図など, 様々な図の作成ができる。
[PlantUML](https://plantuml.com/ja/)

### mermeid
mermeidはpumlと同様、テキストベースで作図ができるライブラリである。
表現力はPlantUMLに比べ少し劣る印象ではあるものの、
javascript製のライブラリであるため、PlantUMLに比べ環境構築が容易で、web系のツールと親和性も良さそう。
（NotionやGithubが対応したのもそういった背景があるのかもしれない。）
[mermeid]()

PlantUMLはかなり表現力のあるツールなので、今回はまずER図に絞って変換器を実装してみることにした。


## 両者の構文の比較 
### Entityの変換
Entityの書き方に対して、両者を比較してみる。
**PlantUML**
```
entity "Entity01" as e01 {
  *e1_id : number <<generated>>
  --
  *name : text
  description : text
}
```

**mermeid**
```
Entity01 {
  e1_id number
  name text
  description text
}
```

mermeidの場合、**entityであることを宣言する必要がなく**、カラムも**タイプと命名の2つだけ**でかなりシンプルだ。
さらにentityに対して**代替の命名ができない**ことも特徴（これがRelation変換部にも関わってくる。）


## Relationの変換
Entityと同じく両者を比較してみる



**PlantUML**
```
e01 ||..o{ e02
e01 |o..o{ e03
```

**mermeid**
```
Entity01 ||--o{ Entity02 : places
Entity01 |o--o{ Entity03 : places
```

まず、mermeidにはentityに対して命名を割り当てる機構がないため
名付けが変わっている場合は元のものに置き換えなくてはならない。

また**それぞれのリレーションに対しては、説明を割り当てなくてはならない**。
（上記でいうところの"places"の部分）

~~さらに（細かな違いではあるが）リレーションに対して『..』は使えないようだ。~~
~~(PlantUMLの場合は破線が表現できる。)~~
こちらは使えるようです🙇‍♂️

### その他
mermeidの場合、`erDiagram`という文を一番最初に入れ、ER図であることを明示的に宣言しなくてはいけないようである。
(PlantUMLに比べ文法が簡潔になっているのもこのためだろう。)


## コード
完成したコードがこちら（今回はwebでの公開を意識してnodeで書いてみた）
``` js
const fs = require('fs')

const text = fs.readFileSync("puml.txt", 'utf8')
const lines = text.toString().split('\n')

let mermeid_text = 'erDiagram\n'

const entity_name_array = []
for (const line of lines) {
  
  if(line.includes('entity')){ // entity開始

    const entity_name = line.split(' ')[1].replace(/[\"]/g,"")

    if(line.includes('as')){ //entityに対して命名がなされている場合
        const entity_replaced_name = line.split('as')[1].replace('{',"").replaceAll(' ',"")
        entity_name_array.push([entity_name, entity_replaced_name])
    }

    mermeid_text += `${entity_name} {\n`

  } else if (line.includes(':')){ // カラム

      columns = line.split(':').map((column) => column.trim())
      column_text = ''
      if(columns.length == 1){
        column_text = `string ${columns[0].replace('*', '')}`
      }else{
        column_text = `${columns[0].replace('*', '')} ${columns[1].split(' ')[0]}`
      }

      mermeid_text += `  ${column_text}\n`

  } else if(line == '}'){ //entity終了

    mermeid_text += '}\n'

  } else if (line.includes('--') && line.length <= 4){
    
    // 不要なので無視

  } else if (line.match(new RegExp('[o|}]{0,2}(..|--)[o|{]{0,2}')) != null) { //relation
    new_relation = `${line.replace('..', '--')} : places\n`
    entity_name_array.forEach((entity_name_el) => {
      new_relation = new_relation.replace(entity_name_el[1], entity_name_el[0])
    })
    mermeid_text += new_relation
  } else {
    mermeid_text += `${line.replace('..', '--')}\n`
  }
}

// 書き込み
fs.writeFile("mermeid.txt", mermeid_text, (err) => {
    if (err) throw err
    console.log('正常に書き込みが完了しました')
});
```

## 今後やろうとしていること
### package文への対応
PlantUMLのER図ではpackageの作成ができるが、mermeidは未対応のよう。
したがってPlantUMLにpackageに関する文があった場合、それを取り除く必要がある。
が、ここまでくると１行ごとの文解析では間に合わないので、良い解析の仕方がないかを模索中。

### webでの公開
上記の通りmermeidはwebとの親和性が高い。
せっかくなので、puml to mermeidの変換器をwebで公開してみようと思う。

### ER図以外への対応
こちらも上述の通り、PlantUMLは ER図以外にもシーケンス図など様々な図をサポートしている。
余裕があればこれらの図に関しても変換器を作ってみたい。