氏名をローマ字に変換しようとして、ローマ字の闇に飲み込まれた

大量の氏名（のふりがな）をローマ字に変換する必要が生じたため そういう gem が無いか探してみたものの、一般的な文章をローマ字に変換するものはあった（例：[Romaji](https://github.com/makimoto/romaji)）ものの 「氏名」に特化したものは見つからなかったので、せっかくだから自分で作ってみました。

- [Namae2Romaji](https://github.com/nodai2hITC/namae2romaji)

これを使うことで、99% の氏名を適切にローマ字に変換することができます！

…さて、『「氏名」に特化？普通の文章でも氏名でもローマ字なんて同じでしょ？』『「99% の氏名」？ローマ字なんて 100% 正確に変換しろよｗｗｗ』などと思った方もいらっしゃることでしょう。

ところが、調べてみるとなかなかにローマ字の世界は闇が深かったのです…

# 氏名のローマ字変換について

ローマ字氏名が必要になるときといえば、パスポートの申請です。外務省がパスポート申請における表記について説明してくれていますが、これによると一般的なローマ字とは少し違うルールが採用されているようです。

- [ヘボン式ローマ字綴方表](https://www.ezairyu.mofa.go.jp/passport/hebon.html)

このルールに基づいて変換をしていこうと思います。

私の書いた以下のコードを使って説明していきます。[^1]

[^1]: 実際にはこの前に、文字コード変換およびひらがな→カタカナ変換が入ります。

```ruby
kana.gsub(/(?<=[オコソトノホモヨロヲゴゾドボポョォ])オ\z/){ "o" }
    .gsub(/(?<=[オト])オ/){ oh ? "h" : "" }
    .gsub(/(?<=[オコソトノホモヨロヲゴゾドボポョォ])ウ/){ oh ? "h" : "" }
    .gsub(/(?<=[ウクスツヌフムユルヴグズヅブプュゥ])ウ/){ "" }
    .gsub(/[ァ-ヴー－\-][ァィゥェォャュョ]?/, ConversionTable)
    .gsub(/ッ(.)/){ ($1 == "c" ? "t" : $1) + $1 }
    .gsub(/n(?=[bmp])/){ "m" }
```

## 基本的な変換

ローマ字変換は基本的には「ア」を『a』に「キャ」を『kya』に…といった具合に 1 文字（ただし後ろに小さいァィゥェォャュョが続く場合は 2 文字）ごとに変換すれば良いだけです。

先のコードで言うと、この部分です。

```ruby
    .gsub(/[ァ-ヴー－\-][ァィゥェォャュョ]?/, ConversionTable)
```

ConversionTable というのは `{ "ア" => "a", ... }` といったカタカナとローマ字の間の基本的な変換規則を収めた連想配列になっています。なお、「ッ」は後で別の方法で変換するので `{ ..., "ッ" => "ッ"}` としてここでは変換しないようにしています。

## 撥音（ン）

「ン」は基本的には『n』に変換すれば良いのですが、例外として『b, m, p』の前では『m』に変換することになっています。以下の部分でその処理をおこなっています。

```ruby
    .gsub(/n(?=[bmp])/){ "m" }
```

## 促音（ッ）

「ッ」は基本的には後ろの子音に変換すれば良いのですが、例外として『chi, cha, chu, cho』の前では『t』に変換することになっています。ただし、ローマ字では先の４つ以外には『c』から始まるものはないので、後ろが『c』かどうかだけをチェックすれば充分です。以下の部分がその処理をおこなっています。

```ruby
    .gsub(/ッ(.)/){ ($1 == "c" ? "t" : $1) + $1 }
```

…ところで、「ッ」で終わる名前とか「ッ」の後に母音が来るような名前とか無いですよね…

## 長音（oオ、oウ、uウ）

長音は、「オ」「ウ」の長音は原則表記しないことになっています。[^2] つまり「オオノ」「コウタ」「ヒュウガ」などの氏名は『ono』『kota』『hyuga』になります。

[^2]: ちなみに「ニイナ（新菜）」のような「イ」の長音は表記しますが、「ニーナ」のような「ー」の長音は発音的には同じであっても表記しません。

ただし例外があり、末尾の「oオ」（「セノオ（妹尾）」など）は『oo』になります。以下の部分でその処理をおこなっています。

```ruby
kana.gsub(/(?<=[オコソトノホモヨロヲゴゾドボポョォ])オ\z/){ "o" } # 末尾の例外処理
    .gsub(/(?<=[オト])オ/){ oh ? "h" : "" } # oオ
    .gsub(/(?<=[オコソトノホモヨロヲゴゾドボポョォ])ウ/){ oh ? "h" : "" } # oウ
    .gsub(/(?<=[ウクスツヌフムユルヴグズヅブプュゥ])ウ/){ "" } # uウ
```

ちなみに「oオ」「oウ」については『oh』と表記することも許されているようなので、オプションでそこを切り替えられるようにしています。

『「oオ」のところがおかしくない？』と思われた方、鋭いです。これについては後で説明します。

# 本題：闇の部分

先の外務省資料に書かれたルールは以上です。「なんだ簡単じゃないか」と思われた方もいるかと思います。

ところが、問題は先ほどの「長音」。確かに「長音」であるなら、上述のルールに従うだけです。

が、その前に「長音かどうか」という判断を入れなければならないのです。ここからが闇の領域…

## 長音ではない「oオ」「oウ」「uウ」

「oオ」「oウ」「uウ」は末尾例外を除けば一律に「オ・ウ」を消して『o,u』にすればいいかというとそうではなく、「oオ」「oウ」「uウ」という形をしていても長音ではない、というケースがあり得るのです。

例えば、「ヒロオカ（広岡）」「コウチワ（小団扇）」「マツウラ（松浦）」。[^3] これらは「oオ」「oウ」「uウ」を含んではいますが、「ヒロ」＋「オカ」のように分かれるものであり「ロー」と伸ばされているわけではない、と判断され『hirooka』になるのです。

[^3]: 「ヒロオカ」以外の2つは[埼玉県パスポートセンター](https://www.pref.saitama.lg.jp/passport/kinyure/romajihyoki.html)の例より。

人間が漢字を見れば比較的簡単な話なのですが、どうすればこれを機械に判断できるのか…

### カナだけで対応できるか？

上に書いたとおり、人間が「漢字を」見れば比較的簡単です。ではフリガナだけしか無かった場合、確実に判断する方法はあるでしょうか？

…残念ながら、おそらくは無いと思います。例えば先に書いた「小団扇」は「コ」＋「ウチワ」なので長音ではありませんが、仮に「高知輪」と書いて「コウチワ」と読む名字があったとしましょう。[^4] 同じ「コウチワ」でも前者は『kouchiwa』後者は『kochiwa』と変換しなければならないのです。つまり、カナだけで対応するのは不可能と言えます。

[^4]: 「小団扇」は実在する名字のようですが、「高知輪」は説明のために作った名字であり実在するかどうかは分かりません。

### 漢字データも与えたら？

では、カナだけではなく漢字データも与えるようにすればどうでしょうか。漢字の一般的な読み方をデータとして持っておき、そこから「マツウラ」＋「松浦」→「松（マツ）浦（ウラ）」というようにカナのどの部分がどの漢字に対応しているかを調べ、漢字ごとに変換する…とすれば上の例はうまく行きそうです。（すごく面倒そうですが…）

ところが、外務省の資料には「ミソノウ（御園生）」という名字例が出てきます。これは「御（ミ）園（ソノ）生（ウ）」だと思うんですが、ローマ字にすると『misono』だそうです。**どういうことだよ！**

### 「ヒロオカ」など、長音ではない「oオ」への対応

そもそも長音の「oオ」は、「オオ（大など）」と「トオ（遠）」の 2 種類しか無いような気がします。ですから下のように、「oオ」のルールは「オオ」と「トオ」だけに適用するようにすれば対応できる名前を増やせそうです。

```ruby
    .gsub(/(?<=[オト])オ/){ oh ? "h" : "" } # oオ
```

でも仮に「ヒトオカ（人丘）」のような名字があったとしたら、やはりこれでは駄目です。

# 結論として

そんなわけで私はこうした氏名への対応は諦め、99% の変換率で満足することにしました。もしより上の変換率を目指すのであれば、変換が難しい名字のデータを持っておいて例外的に変換するのがまぁまぁ現実的かなぁと思います。闇を覗く覚悟のある方の挑戦をお待ちしております。