UTF-8 の文字列をできる限り Shift_JIS に変換したい

Shift_JISCSV で連携する外部サービスがあり、DB では UTF-8 でテキストを持っていたため文字コードを変換する必要が生じた。 ところが UTF-8 に存在する多くの文字は Shift_JIS に対応がないため変換することができない1

そこで、事前に NFKC 形式で Unicode 正規化することで変換可能な文字を増やすことを試みた。 まずは Unicode 正規化の前提として、Unicode の正準等価と互換等価について説明する。

以降の U+16進数 という表記は Unicode のコードポイント (文字に ID のようなものが割り当てられている) を示す。 また、コードポイントに対応する文字の詳細は https://codepoints.net/ といったサイトで確認することができる。

正準等価

例として、ひらがなの「が」について考える。Unicode では「が」を表現する方法が 2 つある。

これらは文字としては同じ「が」を示すが、違うコードで表現されている。このように同じ文字を別のコードで表現するものを正準等価と言う。

また、このとき前者は Shift_JIS に変換できるが、後者は濁点 U+3099 の対応が Shift_JIS になく変換することができない。

ちなみに単体の濁点は U+309B に別に存在していて、これは Shift_JIS に変換することができる。 U+3099 はあくまで他の文字と結合するための存在 (結合文字と呼ばれる) であり、複数のコードポイントを結合して 1 つの文字を表現するような概念は Shift_JIS にはないため結合文字の対応がないのである。

互換等価

次に、単位の cm について考える。これも 2 つの表現がある。

これらは意味としては同じものを指すが、文字列としては異なる表現をされている。このようなものを互換等価と言う。

また、このとき後者は Shift_JIS に対応がない。

Unicode 正規化

さて、Unicode 正規化に話を戻す。Unicode 正規化には 4 種類の正規化形式があるのだが、ここでは先に挙げた NFKC 形式についてのみ考える。 NFKC 形式で正規化すると、互換等価性に基づいて文字が分解され、更に正準等価性に基づいて文字が結合される。

前述の例に当てはめると、組文字の「cm」U+339D はアルファベットに分解されて U+0063 U+006D となる。

また、複数のコードポイントを結合した文字 (結合文字列と呼ばれる) は合成される。「か」と濁点の組み合わせである U+304B U+3099 は「が」を表す U+304C となる。

これはつまり、組文字や結合文字列は正規化によって Shift_JIS に変換することが可能になるということだ。

困ったこと

他にも、例えば全角文字は半角文字に変換される。これは全角文字と半角文字が互換等価であるためだ。 全角の円記号U+FFE5 は半角の円記号 U+00A5 に変換されるのだが、Shift_JIS では半角の円記号が定義されていない。 正規化によって元々は変換できていたものが変換できなくなってしまったのである。

これについては Shift_JIS に変換する際にフォールバックを設定することで回避した。 今のところ問題になっているのは円記号のみで、増えたとしても多くはないであろうことからフォールバックを個別に設定していけばよいと判断した。

まとめ

文字コードを変換する際、NFKC 形式で Unicode 正規化することで Shift_JIS に変換できる文字を増やすことができた。 Shift_JIS に限った話ではないので他の文字コードにも応用できると思われる。

参考

用語の日本語訳は https://www.unicode.org/terminology/term_en_ja.html を参考にした。


  1. 正確には Unicode から JIS X 0208