環境依存文字をJIS X 0208の異字体に置換するソフトを作ってみた

`CP932`の環境依存文字（機種依存文字）を、`JIS X 0208`（JIS基本漢字）の異字体・俗字に置き換えるソフトを **C#** で作ったので記事にする。

# 予備知識
本稿では、次の知識があるものとして話を進める。

- **文字符号化方式**（エンコーディング）と**符号化文字集合**（キャラクタセット）の違いを知っている。
- `JIS X 0208`は**符号化文字集合**のひとつであり、その**文字符号化方式**には`Shift_JIS`、`EUC-JP`、`ISO-2022-JP`などがあること。
- `Unicode`は**符号化文字集合**のひとつであり、その**文字符号化方式**には`UTF-8`、`UTF-16`などがあること。
- `CP932`は、`Shift_JIS`の**符号化文字集合**をマイクロソフト社が独自に拡張したものであること。

一般に「文字コード」と言われれば**文字符号化方式**を指すが、厳密には異なる。
もし新人SEが`Unicode`と`UTF-8`の違いを質問してきたら、それは「文字コード」が**文字符号化方式**と**符号化文字集合**で構成されていることを理解していないということなので、丁寧に説明してあげよう。

なお、`CP932`については、
https://qiita.com/kasei-san/items/cfb993786153231e5413
の記事が詳しいので、そちらを参照して欲しい。

# 開発のきっかけ
今の[eLTAX（地方税の電子申告システム）](https://ja.wikipedia.org/wiki/ELTAX)では、`JIS X 0208`で定義されていない環境依存文字、半角カナ、JIS第3水準漢字、JIS第4水準漢字を扱えないので、これらを**似たような文字**に置き換えてしまおうというのが開発の発端。
~~ちなみに総務省発表によると、扱える文字の不足問題は2019年9月に改善される予定とのことで今後に期待するとしよう。~~
<font color="red">現在は e-Tax（国税の電子申告システム）と同水準に改善され、この問題は解消している。</font>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/30f22df6-da96-99bd-25e9-fdf2269ee55c.png)
なお、[e-Tax（国税の電子申告システム）](https://ja.wikipedia.org/wiki/E-Tax)では、`JIS X 0221`を`UTF-8`で符号化した文字コードを扱えるため上図に示した文字では問題は発生しない。ただし、「**𠮷**」（**土**の下に**口**。某牛丼チェーンでも使われている文字）のような**サロゲートペア**の文字はやはり扱えない。

# そもそも異字体・俗字とは何か
異字体・俗字に明確な定義づけがあるわけではない。
同じ意味の漢字ではあるものの、字体が少し異なっているものとし、ここでは、**旧字体**や**古字**も異字体に含まれると定義する。

ソフトの性質上、人名と地名で使われる漢字に集中するわけだが、まずは該当文字を洗い出し、文字単位で変換辞書を作成するところから始めた。
`CP932`で拡張された文字が対象なので、`JIS X 0208`に存在する文字は除外する。

## 変換される漢字の例
`JIS X 0208`に存在しないので変換する。

- <font color="red">髙</font>橋 一生 :arrow_forward: <font color="red">高</font>橋 一生
- <font color="red">濵﨑</font> あゆみ :arrow_forward: <font color="red">浜崎</font> あゆみ
- 綾<font color="red">瀨</font> はるか :arrow_forward: 綾<font color="red">瀬</font> はるか
- <font color="red">德</font>永 英明 :arrow_forward: <font color="red">徳</font>永 英明

## 変換されない漢字の例
旧字体ではあるが`JIS X 0208`にも存在する漢字なので変換しない。

- <font color="red">齊</font>藤 由貴 :arrow_forward: <font color="red">斉</font>藤 由貴
- 森山 未<font color="red">來</font> :arrow_forward: 森山 未<font color="red">来</font>
- <font color="red">眞</font>木 よう子 :arrow_forward: <font color="red">真</font>木 よう子
- <font color="red">櫻</font>井 翔 :arrow_forward: <font color="red">桜</font>井 翔
- <font color="red">氣</font>志<font color="red">團</font> :arrow_forward: <font color="red">気</font>志<font color="red">団</font>
- <font color="red">國學</font>院大<font color="red">學</font> :arrow_forward: <font color="red">国学</font>院大<font color="red">学</font>

## 中国の人名など
日本国内で流通していない漢字、中国の一部の人名用漢字については、ピンイン、中国語読みのカナ表記、日本語読みのカナ表記のいずれかに変換できるようにした。
日本に伝えられた漢字の読み方（音読み）には、[呉音](https://ja.wikipedia.org/wiki/%E5%91%89%E9%9F%B3)（主に百済人によって伝えられた中国南方系の読み方）、[漢音](https://ja.wikipedia.org/wiki/%E6%BC%A2%E9%9F%B3)（奈良～平安時代に伝えられた中国北方系の読み方）、[唐音](https://ja.wikipedia.org/wiki/%E5%94%90%E9%9F%B3)（主に鎌倉時代に伝えられた江南浙江地方の読み方）があるが、中国の人名を日本語で読む場合は漢音が多いので、ソフトでは漢音に統一した。

## 変換できない文字
苗字の棈松（アベマツ）の「**棈**」、地名の荢千場（ウホシバ）の「**荢**」、苗字と地名の双方に使われている[橳島（ヌデシマ）](https://ja.wikipedia.org/wiki/%E3%81%AC%E3%81%A7%E5%B3%B6)の「**橳**」などは、`JIS X 0208`の中に適当な文字が見つからなかった。「橳」の**俗字**として「椦」を確認できたものの、典拠が不明な[幽霊文字](https://ja.wikipedia.org/wiki/%E5%B9%BD%E9%9C%8A%E6%96%87%E5%AD%97)なので採用しなかった。
また、岩手県には霻霳（ホウリョウ）という、`JIS X 0208`にない文字だけで構成された地名があり「豊隆」と書き換えた文献も確認しているが、ソフトは単語単位ではなく文字単位で見るので「豊隆」には変換されない。

変換が必要なのに変換できなかった文字、つまり辞書に無い文字は、ユーザーの判断に委ねることにした。

# ソースコード
**C#** のソースコードを一部だが抜粋する。

## 文字チェック関数

`CP932`で拡張された文字なら 0 以外を返す。
Encodingに`Shift_JIS`を指定しているが、これは **.NET Framework** に該当するコードページが無いからで、要は`CP932`と同じである。
**.NET** で `CP932`と`Shift_JIS`を区別する関数としても使えるだろう。

```c#:C#
Encoding sjis = Encoding.GetEncoding("Shift_JIS", EncoderFallback.ExceptionFallback, DecoderFallback.ReplacementFallback);
private int getBadChar(char c) {
    byte[] bytes = sjis.GetBytes(c.ToString());
    if (bytes.Length == 1) return 0;  // 半角なら
    if (BitConverter.IsLittleEndian) Array.Reverse(bytes);  // 上位バイトと下位バイトを入れ替える
    int code = BitConverter.ToUInt16(bytes, 0);

    // 文字コードチェック（チェック範囲は下位１バイトが 0x40～0x7e、0x80～0xfc にあるもの）
    if ((code >= 0x81ad && code <= 0x81b7) || (code >= 0x81c0 && code <= 0x81c7) || (code >= 0x81cf && code <= 0x81d9) ||
        (code >= 0x81e9 && code <= 0x81ef) || (code >= 0x81f8 && code <= 0x81fb) || (code >= 0x81fd && code <= 0x824e) ||
        (code >= 0x8259 && code <= 0x825f) || (code >= 0x827a && code <= 0x8280) || (code >= 0x829b && code <= 0x829e)) {
        return code;  // 記号、桁、ラテン
    }
    if ((code >= 0x82f2 && code <= 0x82ff) || (code >= 0x8397 && code <= 0x839e)) {
        return code;  // ひらがな、カタカナ
    }
    if ((code >= 0x83b7 && code <= 0x83be) || (code >= 0x83d7 && code <= 0x83df) || (code >= 0x8461 && code <= 0x846f) ||
        (code >= 0x8492 && code <= 0x849e) || (code >= 0x84bf && code <= 0x889e)) {
        return code;  // ギリシャ文字、キリル文字、特殊文字
    }
    if ((code >= 0x9873 && code <= 0x989e) || (code >= 0xeaa5)) {
        return code;  // 漢字
    }
    return 0;
}
```

## 文字変換関数
上述の文字チェック関数で 0 以外を返した文字は、文字変換関数に渡す。
これで`JIS X 0208`で表現できる文字列となる。定義が無ければ *null* を返す。

```c#:C#
private string replaceChar(char badChar) {
    switch (badChar) {
        case '髙': return "高";
        case '﨑': return "崎";
        case '濵': return "浜";
        case '賴': return "頼";
        case '瀨': return "瀬";
        case '德': return "徳";
        case '蓜': return "配";
        case '昻': return "昂";
        case '桒': return "桑";
        case '栁': return "柳";
        case '犾': return "犹";
        case '琪': return "棋";
        case '裵': return "裴";
        case '魲': return "鱸";
        case '羽': return "羽";
        case '焏': return "丞";
        case '祥': return "祥";
        case '曻': return "昇";
        case '敎': return "教";
        case '澈': return "徹";
        case '曺': return "曹";
        case '黑': return "黒";
        case '塚': return "塚";
        case '閒': return "間";
        case '彅': return "薙";
        case '匤': return "匡";
        case '冝': return "宜";
        case '埇': return "甬";
        case '鮏': return "鮭";
        case '伹': return "但";
        case '杦': return "杉";
        case '罇': return "樽";
        case '柀': return "披";
        case '﨤': return "返";
        case '寬': return "寛";
        case '神': return "神";
        case '福': return "福";
        case '礼': return "礼";
        case '贒': return "賢";
        case '逸': return "逸";
        case '隆': return "隆";
        case '靑': return "青";
        case '飯': return "飯";
        case '飼': return "飼";
        case '緖': return "緒";
        case '埈': return "峻";
        case '①': return "(1)";
        case '②': return "(2)";
        case '③': return "(3)";
        case '④': return "(4)";
        case '⑤': return "(5)";
        case '⑥': return "(6)";
        case '⑦': return "(7)";
        case '⑧': return "(8)";
        case '⑨': return "(9)";
        case '⑩': return "(10)";
        case '⑪': return "(11)";
        case '⑫': return "(12)";
        case '⑬': return "(13)";
        case '⑭': return "(14)";
        case '⑮': return "(15)";
        case '⑯': return "(16)";
        case '⑰': return "(17)";
        case '⑱': return "(18)";
        case '⑲': return "(19)";
        case '⑳': return "(20)";
        case 'Ⅰ': return " I ";
        case 'Ⅱ': return " II ";
        case 'Ⅲ': return " III ";
        case 'Ⅳ': return " IV ";
        case 'Ⅴ': return " V ";
        case 'Ⅵ': return " VI ";
        case 'Ⅶ': return " VII ";
        case 'Ⅷ': return " VIII ";
        case 'Ⅸ': return " IX ";
        case 'Ⅹ': return " X ";
        case '㎜': return "mm";
        case '㎝': return "cm";
        case '㎞': return "km";
        case '㎎': return "mg";
        case '㎏': return "kg";
        case '㏄': return "cc";
        case '㎡': return "m2";
        case '㍻': return "平成";
        case '㋿': return "令和";
        case '〝': return "“";
        case '〟': return "”";
        case '№': return "No.";
        case '㏍': return "K.K.";
        case '℡': return "Tel";
        case '㈱': return "(株)";
        case '㈲': return "(有)";
        case '㈹': return "(代)";
        case '㍾': return "明治";
        case '㍽': return "大正";
        case '㍼': return "昭和";
    }
    return null;
}
```

---
なお、完成したソフトの方は、オンラインソフトウェア流通サイト **Vector** で無償配布させていただいた。
興味のある人は使ってみて欲しい。
https://www.vector.co.jp/soft/winnt/business/se518540.html
![tool.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/28f88c85-0e63-aff5-e9e6-bb7339619efb.png)
※人名・住所は架空のもの。

# 参考URL
- ユニコードコンソーシアム　JIS X 0208 (1990) to Unicode
http://www.unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/JIS0208.TXT