旧字体に変換したのに元へ戻る — 正規化で消える68字

旧字体に直したはずの文章を投稿フォームへ貼ったら、送信した先では新字体に戻っていた。名簿の氏名をデータベースへ入れたら、一部の字だけ形が変わっていた。この現象には原因がはっきりあります。常用漢字表が示す康熙字典体386組のうち68組は、旧字体の側がUnicodeの「CJK互換漢字」という区画にあり、正規化を一度通すだけで新字体へ潰れるからです。フォントの不足が原因ではありません。規格がそう定めています。

68字はどこから来たのか

CJK互換漢字はU+F900からU+FAFFまでの区画です。最新のUnicode文字データベース(UnicodeData.txt)を落として数えると、割り当てられているのは472字でした。区画の名前のとおり、ここは新しい字を入れる場所ではありません。すでに統合漢字として符号位置を持っている字に、既存の文字コード規格との往復のためだけに二つめの席を与えた場所です。

誰の要請で入ったのかは、Unihanデータベースの出典欄で追えます。区画全体では韓国の出典を持つ字が257字と最も多く、北朝鮮が104字と続きます。ところが常用漢字表の康熙字典体にあたる68字を抜き出すと、68字すべてに日本の出典欄(kIRG_JSource)が付いていました。内訳は61字がJIS X 0213:2004の第3水準、7字が文字情報基盤整備事業の番号です。つまりこの68字は、日本の規格の側が「統合漢字とは別の席が要る」と申告したために生まれた席です。

席が二つあれば、どちらを指しているのかが分かれます。Unicodeはこれを放置せず、互換漢字の側に正準分解という対応付けを与えました。UnicodeData.txtの第6欄がそれで、68字すべてについて、分解先が新字体の符号位置そのものを指していることを1字ずつ確認しました。

4つの正規化形すべてが潰します

正規化には4つの形があります。NFCとNFDが正準等価にもとづく形で、NFKCとNFKDは互換等価まで踏み込む形です。ふつうは「NFKCは全角英数や丸数字まで潰すから危ない、NFCなら安全」と説明されます。

ところが互換漢字については、その区別が助けになりません。68字を4形すべてにかけて数えたところ、結果はこうでした。

正規化の形新字体へ潰れた組数
NFC68 / 68
NFD68 / 68
NFKC68 / 68
NFKD68 / 68

正準分解を持つ以上、いちばん穏やかなNFCでも消えます。しかも一度分解された単独の字は、合成の段階で元へ戻りません。単独分解は合成の対象から外されているからです。

そして、これは将来直る種類の問題ではありません。Unicodeの安定性方針は「いったん符号化された文字の正準結合クラスと分解対応は、正規化を不安定にする形では変更されない」と定めています。つまり68字が潰れることは仕様として固定されており、規格側の修正を待つ道はありません。

同じ区画でも消えない12字があります

区画に入っているから危ない、という覚え方をすると外します。472字を数え直すと、正準分解を持たない字が12字ありました。U+FA0E・U+FA0F・U+FA11・U+FA13・U+FA14・U+FA1F・U+FA21・U+FA23・U+FA24・U+FA27・U+FA28・U+FA29の12字です。名字の表記でよく見かける、いわゆる立つ崎(U+FA11)もここに含まれます。Unihanはこの字の読みを「さき」、U+5D0E(崎)の意味上の異体としています。

この12字を4形すべてにかけましたが、12字とも1字も変わりませんでした。危険の有無を決めているのは区画ではなく、その字が正準分解を持っているかどうかです。

どこで正規化がかかるのかを測りました

「正規化はいつの間にかかかる」と言われますが、どこでもかかるわけではありません。互換漢字のU+FA45を混ぜた文字列を、経路ごとに通して符号位置を見ました。

通した経路符号位置
JSONへの書き出しと読み戻しU+FA45のまま
ファイルの本文として保存し読み戻すU+FA45のまま
ファイル名として保存し一覧で読み戻すU+FA45のまま
パーセント符号化と復号U+FA45のまま
URLのパス部分U+FA45のまま
URLのホスト名U+6D77へ潰れた

ホスト名だけが例外でした。国際化ドメイン名の変換は内部で正規化を行うため、U+FA45を含むホスト名はPunycodeへ直した時点で統合漢字のものと同じ文字列になります。先ほどの12字のうちU+FA11で同じことを試すと、こちらはホスト名でも符号位置が保たれました。分解対応の有無が、ここでもそのまま結果を分けています。

つまり正規化は空気のようにかかっているのではなく、文字を照合する必要のある門でかかります。投稿フォームの入力検査、データベースの照合順序、検索の索引、識別子の突き合わせが、その門です。手元の編集画面から送信ボタンまでは何も起きず、受け取った側の門で静かに置き換わります。

広告

気づけない理由は、二つの答えが食い違うことです

同じ2字を、機械の別々の口に尋ねてみました。

尋ね方答え
厳密等価(===)別の字
部分一致(includes)別の字
正規表現別の字
集合に入れたときの要素数2
日本語の照合(Intl.Collator)同じ字(戻り値0)

照合の口だけが「同じ」と答えます。しかも字形を厳しく見る設定にしても0のままでした。この食い違いが、事故を見えなくします。検索窓に新字体で打てば旧字体の行も出てくるので、利用者からは正しく入っているように見えます。一方で完全一致の突き合わせや重複判定は別物として扱うため、名寄せの段階で初めて表面化します。表示は正しく、検索も通り、突き合わせだけが合わないという壊れ方です。

この記事を書くときにも消えました

机上の話ではないことは、この原稿の執筆中に二度確かめられました。

一度目は、道具の対応表を作るときに見つけた事実です。同じ常用漢字表から起こされた英語版の一覧と1字ずつ突き合わせたところ、共通する357字のうち62字で相手側の「旧字体」欄が新字体と同じ符号位置になっており、そのうち61字が上の68字でした。表を作る側でさえ、写した経路のどこかで消しています。

二度目は、この原稿そのものです。互換漢字を本文へ直接置こうとしたところ、書き込んだ内容を読み返すと新字体になっていました。どの経路が潰したのかは特定できていないので断定はしませんが、結果として1字も置けませんでした。そのためこの記事では、危ない字を符号位置の表記で書いています。旧字体⇔新字体変換の対応表も同じ理由で、該当する68組だけは\uXXXXのエスケープで書いてあり、新旧が同じ字の対が1組も無いことをテストで見張っています。

異体字セレクタという別の解き方

旧字体の字形を保ったまま正規化を越えたい場合、異体字セレクタ(IVS)という道があります。統合漢字の後ろに見えない選択子を付けて字形を指定する仕組みで、U+6D77にVS17(U+E0100)を足した列は、NFCを通しても符号位置が1つも変わりませんでした。異体字データベースを見ると、この列はAdobe-Japan1のCID+13327として登録されており、U+6D77には8件の異体字列が登録されています。

ただし順番を間違えると効きません。互換漢字の後ろにVS17を付けても守れませんでした。NFCを通すと基底の字だけが潰れ、U+6D77とVS17の列になります。つまり選択子は基底を守る鎧ではなく、統合漢字に付けて初めて意味を持つ札です。

なお旧字体⇔新字体変換にIVSを付ける機能は入れていません。どの選択子がどの字形にあたるかは登録の内容で決まり、その対応を一次資料で裏取りできていないためです。分かっていないことを機械が黙って決めると、この記事が扱っている事故と同じ形になります。

まとめ

最終更新: 2026-09-10

広告