Macで濁点が別文字になる問題の原因と解決法(NFCとNFDの違い)

Macで濁点が別文字になる問題の原因と解決法(NFCとNFDの違い)

2分で読める テック
最終更新:

Webアプリケーション開発中に、日本語タイトルの検索機能を実装したとき、濁点を含む文字列だけ検索がヒットしない問題に遭遇しました。原因はMacのNFD正規化でした。

起きたこと

検索フォームの入力文字列とデータベースのタイトルを照合する実装で、アルファベットや英数字は問題なく動いていたのに、「ブログ」「プログラム」のような濁点・半濁点を含む文字列だけ一致しませんでした。

Macの濁点・半濁点問題

Macのファイルシステム(APFSおよびHFS+)は、日本語の濁点・半濁点をNFD形式で処理します。

NFD形式では、例えば「ブ」を「フ」+「゛」(2文字の組み合わせ)として扱います。一方、多くのデータベースやOSでは「ブ」を1文字(NFC形式)として扱います。この不一致が検索ミスの原因です。

補足: macOS 10.13 High Sierra以降のデフォルトはAPFSですが、この濁点問題はAPFSでも同様に発生します。

NFC/NFDとは

UnicodeはNFC・NFDという2種類の「正規化形式」を定めています。

  • NFC(Normalization Form Canonical Composition): 「が」を1つの合成済み文字として扱う
  • NFD(Normalization Form Canonical Decomposition): 「が」を「か」+「゛」の2文字として扱う

同じ文字列でもNFCとNFDでバイト列が異なるため、単純な文字列比較では一致しません。

対処法

検索時に文字列をNFCに正規化してから比較します。

Pythonの場合:

import unicodedata

def normalize(text: str) -> str:
    return unicodedata.normalize("NFC", text)

# 使用例
query = normalize(request.params["q"])

Rubyの場合:

# ActiveSupportを使う場合
query = params[:q].unicode_normalize(:nfc)

# 標準ライブラリを使う場合
query = params[:q].unicode_normalize

JavaScriptの場合:

const query = input.normalize("NFC");

入力値とデータベースの値の両方を同じ正規化形式に揃えることで、OS差異に依存しない文字列比較ができます。

質問・リクエストを送る

記事についての質問や、取り上げてほしいテーマがあればお気軽にどうぞ。いただいた質問はブログ記事として回答し、Q&Aページで公開することがあります。