Unicode ノーマライザー

Unicode には 4 つの正規化形式があり、交換可能ではありません。NFC と NFD は同じ文字を 1 コードポイントと基底+結合記号の間で並べ替え、NFKC と NFKD はさらに進んで、単に似て見えるだけの文字を書き換えます — fi 合字、全角 A、囲み ①、ローマ数字 Ⅻ。このページは同じテキストを 4 つすべてに通し、何が変わりなぜ変わったかを文字ごとに 1 行で印字し、書記素をコードポイントと UTF-8 バイトに対して数え、選んだ形式の下で 2 つの文字列を比較します。 後半は、まったく現れない文字たちについてです:ゼロ幅スペース、ジョイナ、双方向制御、異体字セレクタ、C0/C1 制御文字、私用コードポイント、壊れたサロゲート片。それぞれに位置と名前があり、テキストのコピーから除去するオプションもあります。
すべての処理はブラウザー内でローカルに行われ — データがサーバーへアップロードされることはありません。

正規化が何か分からない? Unicode 正規化を平易に解説 — なぜ é が 1 文字にも 2 文字にもなり得て、それがログインを壊すのか。
報告と比較に使う形式:
2 つのボックスを比較(適用形式):
NFC (まだ何もありません)
NFD (まだ何もありません)
NFKC (まだ何もありません)
NFKD (まだ何もありません)
まだ何も比較していません。2 つの文字列を貼り付けて2 つの文字列を比較を押してください。
1 番目の文字列(正規化済み) (まだ何もありません)
2 番目の文字列(正規化済み) (まだ何もありません)
まだ何もスキャンしていません。不可視・危険文字をスキャンを押し、それらの文字を除去したテキストが欲しければ除去を押してください。
選択クラスを除去したテキスト (まだ何もありません)
最初のボックスで Ctrl+Enter を押すとアクティブなモードが実行されます。
モードを選び、そのアクションボタンをクリックしてください。正規化はテキストの 4 形式すべてと文字ごとの行を書き、比較は正規化後に 2 つの文字列が同じかを告げ、スキャンは現れない文字を一覧し、それらを除去できます。セルフテストボタンは、このページのデータファイルの記録ケースすべてと、不可視・制御テーブルが名指すすべての文字を再実行し、件数をここに報告します。
すべての文字、1 文字 1 行:その文字が何か、4 つの形式がそれに何をするか、そしてその理由。網掛けの行は少なくとも 1 つの形式が変更する文字です。NFD 列は、その文字が分解されるとき何に分解されるかを示します。
それは何文字?同じテキストを 4 通りに数えます:書記素クラスタ(読み手が数えるもの)、コードポイント(Unicode が割り当てるもの)、UTF-16 単位(JavaScript が添字に使うもの)、UTF-8 バイト(伝送されるもの)。正規化は、最初の列を変えずに 2 番目と 3 番目の列の間で数を動かします。
見つかったもの(出現順):位置はコードポイントを数え、クラスはその文字が属するもの、備考はその文字が次にテキストを読むものに何をするかを述べます。
このページが怪しいとみなすクラス:テーブルはスキャナー自身から印字されるため、このページの言葉とボタンの挙動がずれることはありません。深刻度は比較への影響についてであって、その文字が無実である可能性についてはではありません。
私用文字は他と別の理由で怪しいとして一覧されます:表示はされますが、合意された意味がないため、同じコードポイントが、それを定義するフォントごとに違う絵になるのです。
このページが受け付ける入力の大きさと理由:正規化は文字列全体の再構築で、このページはそれを数回に加えスキャンと書記素カウントも行うため、コストは入力長に比例して増えます。下の上限が存在するのは、文字ごとのテーブルが文書内に描画されるためであり、100 万行のテーブルこそが実際にブラウザーを止めるものだからです。
Unicode 正規化とは?

Unicode は一部の文字を複数の方法で書けます。文字 é は単一のコードポイント U+00E9 にも、普通の e に結合アキュート U+0301 が続くものにもできます。2 つは画面上同じに見え、同じに印刷され、しかし文字列としては異なります:長さが違い、UTF-8 バイトが違い、'é' === 'é' は false です。

それは珍現象ではなく、バグのクラスです。どちらの形式が得られるかは、キーボード配列、オペレーティングシステム、入力メソッド、そしてテキストがワープロを通してコピーされたかどうかに依存します。文字列をバイト単位で比較するパスワードチェック、データベースの一意索引、ブロックリスト、キャッシュキーは、2 つの綴りを 2 つの異なる値として扱います — そのため、あるデバイスでパスワードを設定したユーザーが別のデバイスからログインできなかったり、「既に使用中」の名前一覧に同じ単語が 2 回現れたりします。

30 秒版:NFC と NFD は同じテキストの 2 つの書き方です — NFC は合成し(e + アキュートが 1 文字に)、NFD は分解します(1 文字が e + アキュートに)。これらは可逆であるよう意図されており、比較と保存に使う 2 形式です。NFKC と NFKD は前 2 者がすることすべてに加え、互換文字も置き換えます:合字 fi が 2 文字になり、½ が 1⁄2 になり、ローマ数字 ⅲ が XII になり、全角 A が普通の A になります。それこそ検索索引やキーワードフィルタが求めるものであり、パスワードに決して適用してはならないものです。
なぜ 1 つではなく 4 形式なのか

この分割が存在するのは、2 つの異なる目標があったからです。テキストの合成と分解は正準(canonical)な変更です:結果は同じ意味を持ち、最初の 2 形式では何も失われません。合字、上付き、囲み文字、全角形式を畳み込むのは互換(compatibility)な変更です:照合の利便性のために行われ、Unicode が異なると言う 2 つの文字列の区別を実際に破壊します。4 形式はこの 2 つの決定のすべての組み合わせです。

上の表はこのページのデータファイルのケース一覧で、プラットフォーム自身の String.prototype.normalize と照合されており、セルフテストがそのすべての行をあなたの目の前で再チェックします。最初の 2 行を一緒に読んでください:同じテキストの 2 つの書き方で、正規化後に同じ文字列になります。

worked 例

これはサンプルボタンが順に示す文字列で、答えとして打ち込まれたものではなくページがライブに正規化したものです。それぞれ、ある形式が目に見える何かをするから選ばれています。

互換文字:NFKC の代償

NFKC と NFKD は非可逆で、その損失は意図的です。これらは「同じ単語か」には答えますが「同じ文字列か」には答えません。3 つの帰結が続き、3 つすべて本番コードで間違えやすいものです:

  • パスワード、トークン、署名を互換形式で保存しない。異なる 2 つの入力が同じ保存値に潰れ、長さや文字チェックを弱いものに変えます。パスワードは NFC で正規化する(またはしない)し、両側にまったく同じことをしてからバイトとして比較してください。
  • 互換畳み込みは 1 文字を複数にできる。合字 fi は 1 コードポイントが 2 文字になるため、正規化前に長さを検証した欄が、正規化後に長くなることがあります。正規化してから検証してから保存してください。
  • NFKC は不可視のものすべてを除去しない。異体字セレクタは NFKC と NFKD を生き延び、ゼロ幅ジョイナもそうです。だからこのページにはスキャンモードがあります:正規化は文字の形と同一性についての問いに答え、スキャナーは何かがそこにあるかどうかについての問いに答えます。
書記素、コードポイント、バイト

正規化はこのうち 2 番目の数を変え、最初は通常そのままです。3 つの数はすべて「テキストの長さ」で、すべて異なります。だから 20 文字制限のフォーム欄が、20 コードポイント、30 UTF-16 単位、47 バイトの文字列に属することがあり得るのです。

書記素列の数は人が数えるものです。それとコードポイント数の差こそスキャナーの主題です:12 コードポイントの名前が、12 個の文字としても、3 個の文字とその間の 9 個の不可視記号としても、1 個の絵文字としても表示され得るのです。

見ることのできない文字

ゼロ幅スペースは幅を占めず、グリフを変えず、それでいてコードポイントを持つ本物の文字です。画面上で同一に見える 2 つの識別子が、それ 1 個で異なり得ます。それが、リスト内の名前が、隣の名前と異なるソート・検索・認証を受ける偽物であり得る仕組みです。双方向制御文字も同様で、文字列を書かれた順序と異なる順序で描画させられます — ファイル名を偽装するのに使われたトリックです。

第 3 モードのスキャナーは、それらすべてを位置付きで一覧します:ゼロ幅・結合文字、双方向制御、異体字セレクタ、特殊な空白、C0/C1 制御文字、私用コードポイント、そして有効なペアの半分でないサロゲート(文字ですらないもの)。クラス表はこのページの上の方にあり、各クラスが何をするかを述べ、除去ボタンはあなたの入力に触れずにそれらのコピーを取り除きます。

このページが捕まえるために作られた間違い
  • 正規化せずに 2 つの文字列を比較する。'é' === 'é' は false です。比較前に両側を同じ形式で正規化し、比較する索引にも同じ形式を使ってください。
  • 片側だけ正規化する。照会だけを正規化して保存値を正規化しない検索は何も見つけず、正規化の問題ではなくデータの問題に見えます。
  • 互換形式でフォームを飾って NFC 安全と呼ぶ。パスワードに NFKC を適用すると、可能なパスワードの空間を狭め、別であるべき入力を併合します。
  • 正規化形式が偽装を直すと仮定する。ゼロ幅ジョイナも異体字セレクタもホモグリフも除去しません — キリル а とラテン a は、NFC が放置する異なるコードポイントです。
  • 文字数を信頼する。ユーザーが見る数、データベース列が強制する数、バイト上限が強制する数は 3 つの異なる数で、上の行はそれらがどれほど離れ得るかを示しています。
  • NFC が得られると仮定する。入力メソッド、ファイル、他のシステムから届くテキストは、そのシステムが生成した形式のままです。このページが NFC で始まるのは、それが Web が使うべきとされる形式だからであって、あなたの入力がそうだからではありません。
クイック回答

「同じに見えるのに、この 2 つの文字列はなぜ等しくないのですか?」

片方が合成文字で、もう片方が基底文字+結合記号で書かれているか、片方がまったく何も描かない文字を含んでいるからです。比較モードに切り替えて、各ボックスに 1 つずつ文字列を入れてください:ページは、正規化後の文字列が最初に異なる位置と、そこにあるものを報告します。

「アプリケーションではどの形式を使うべきですか?」

人が読む、表示する、保存する、検索するものすべてには NFC。W3C が Web に推奨する形式です。背後のコードが基底文字と独立した記号を欲しがるとき(テキスト処理や macOS の一部のファイルシステムで一般的)は NFD が正解です。NFKC や NFKD は、保存ではなく照合する場面だけに使ってください:検索索引、キーワードフィルタ、重複検出。

「既に正規化されている文字列に連結しても、まだ正規化されていますか?」

必ずしも。正規化は連結で保存されません:2 つの NFC 文字列をつなぐと、NFC なら別に書いたであろう列が生まれることがあります。2 番目の文字列の先頭の結合記号が、1 番目の末尾の文字に結合し得るからです。組み立て後にも正規化してください。前にだけではなく。

「正規化は不可視文字を取り除きますか?」

いいえ。1 つの部分的な例外を除いて:NFKC と NFKD はノーブレークスペースを通常のスペースに置き換えます。互換畳み込みの目に見える結果です。それ以外 — ゼロ幅スペース、ジョイナ、双方向制御、異体字セレクタ — は 4 形式すべてを無傷で通過します。それが第 3 モードの存在理由です。

「文字数が変わらないのに、なぜバイト数が変わるのですか?」

2 つの形式が、UTF-8 で異なるバイト数を要する異なるコードポイントから構築されているためです。合成済み é は 2 バイト、分解ペアは 3 バイトです。上のカウント表は入力と各形式について 4 つの数を並べて印字するため、違いは説明ではなく目に見えます。

「このページの結果が正しいとどう分かりますか?」

セルフテストを押してください。このページのデータファイルのすべてのケースを正規化し、記録された関係について 4 形式を相互に再チェックし(NFC は冪等、NFC 文字列の NFD は元の NFD と同じなど)、不可視・制御テーブルが名指すすべての文字をスキャナーに通し、書記素ケースを Intl.Segmenter と照合します。

規格とクレジット

4 形式とその定義は Unicode 標準の Annex 15(Unicode Normalization Forms)から来ています。分解例に使うコードポイントは規格が割り当てたものです。このページは分解テーブルを自前で実装せず、ブラウザー自身の String.prototype.normalize を呼びます。テキスト入力、ソート、比較にプラットフォームが使うのと同じ実装なので、ここで報告されるものが、システムの他の部分が同じ文字列にすることです。書記素クラスタは、ブラウザーが持っていれば Intl.Segmenter から、なければ文書化された近似から来ます。

ページのテキストは本サイトのために書かれました。このページのすべての worked 例はページ自身のコードで生成され、データファイルの記録ケース一覧と照合され、その一覧も同じ文字について Unicode NormalizationTest データと照合されています。不可視・危険文字テーブルは人間が読める警告のために書かれた精選部分集合であって、完全な Unicode セキュリティプロファイル(UTS #39)ではありません。ページは持たない完全性をほのめかす代わりに、そう明言しています。