UTF-8 の文字列をできる限り Shift_JIS に変換したい
Shift_JIS の CSV で連携する外部サービスがあり、DB では UTF-8 でテキストを持っていたため文字コードを変換する必要が生じた。 ところが UTF-8 に存在する多くの文字は Shift_JIS に対応がないため変換することができない1。
そこで、事前に NFKC 形式で Unicode 正規化することで変換可能な文字を増やすことを試みた。 まずは Unicode 正規化の前提として、Unicode の正準等価と互換等価について説明する。
以降の U+16進数
という表記は Unicode のコードポイント (文字に ID のようなものが割り当てられている) を示す。
また、コードポイントに対応する文字の詳細は https://codepoints.net/ といったサイトで確認することができる。
正準等価
例として、ひらがなの「が」について考える。Unicode では「が」を表現する方法が 2 つある。
- 文字通り「が」を示す
U+304C
を用いる - 「か」と濁点を組み合わせて
U+304B U+3099
とする
これらは文字としては同じ「が」を示すが、違うコードで表現されている。このように同じ文字を別のコードで表現するものを正準等価と言う。
また、このとき前者は Shift_JIS に変換できるが、後者は濁点 U+3099
の対応が Shift_JIS になく変換することができない。
ちなみに単体の濁点は U+309B
に別に存在していて、これは Shift_JIS に変換することができる。
U+3099
はあくまで他の文字と結合するための存在 (結合文字と呼ばれる) であり、複数のコードポイントを結合して 1 つの文字を表現するような概念は Shift_JIS にはないため結合文字の対応がないのである。
互換等価
次に、単位の cm について考える。これも 2 つの表現がある。
- アルファベット 2 文字で
U+0063 U+006D
とする - 1 文字で「cm」を表す記号
U+339D
を用いる- https://codepoints.net/U+339D
- Unicode 特有の概念ではないが、複数の文字を 1 文字の領域に詰めた文字を組文字と言う。他には
U+3320
の「㌠」などが挙げられる。
これらは意味としては同じものを指すが、文字列としては異なる表現をされている。このようなものを互換等価と言う。
また、このとき後者は 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 を参考にした。
-
正確には Unicode から JIS X 0208↩