Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15043] ファイルの概要

このコミットは、Go言語の実験的なロケールパッケージ exp/locale/collate に含まれる比較ツール colcmp における、Unicode正規化形式の取り扱いに関する修正です。具体的には、ICU (International Components for Unicode) ライブラリとの比較において発生していた不一致を解消するため、文字列の正規化形式をNFC (Normalization Form Canonical Composition) からNFD (Normalization Form Canonical Decomposition) へと変更しています。

コミット

commit f5154edc539e2405e206105617ef7f6188b7e6a6
Author: Marcel van Lohuizen <mpvl@golang.org>
Date:   Wed Jan 30 21:19:03 2013 +0100

    exp/locale/collate/tools/colcmp: fixes some discrepancies between
    ICU and collate package: ICU requires strings to be in FCD form.
    Not all NFC strings are in this form, leading to incorrect results.
    Change to NFD instead.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/7201043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/f5154edc539e2405e206105617ef7f6188b7e6a6

元コミット内容

exp/locale/collate/tools/colcmp: ICUとcollateパッケージ間のいくつかの不一致を修正。ICUは文字列がFCD形式であることを要求する。全てのNFC文字列がこの形式であるわけではないため、誤った結果につながっていた。代わりにNFDに変更。

変更の背景

このコミットは、Go言語の実験的なロケールパッケージ exp/locale/collate の開発過程で発見された問題に対処しています。collate パッケージは、異なる言語や地域における文字列のソート順序(照合順序)を扱うためのものです。このパッケージの正確性を検証するために、colcmp (collate compare) というツールが使用されていました。

colcmp ツールは、Goの collate パッケージの動作を、業界標準であるICU (International Components for Unicode) ライブラリの動作と比較するために設計されています。ICUは、Unicodeの国際化機能を広くサポートするC/C++ライブラリであり、多くのシステムやアプリケーションで照合順序の基準として利用されています。

比較テストの実行中に、Goの collate パッケージとICUの間で、特定の文字列に対する照合結果に不一致が生じていることが判明しました。この不一致の原因は、文字列のUnicode正規化形式の扱いの違いにあると特定されました。ICUは、照合処理を行う際に、入力文字列が特定の正規化形式、特にFCD (Fast C/C++ Compatible Decomposition) 形式であることを前提としていることが判明しました。しかし、Goの colcmp ツールが当時使用していたNFC (Normalization Form Canonical Composition) 形式の文字列は、必ずしもFCD形式であるとは限りませんでした。この不一致が、比較テストにおける誤った結果、すなわちGoパッケージとICUの間の差異として現れていたのです。

この問題を解決し、colcmp ツールがICUとの正確な比較を行えるようにするために、Goの colcmp ツールが文字列を正規化する際に、NFCではなくNFD (Normalization Form Canonical Decomposition) を使用するように変更する必要がありました。NFDは、FCDの要件を満たすため、この変更によってICUとの互換性が向上し、比較テストの信頼性が確保されることになります。

前提知識の解説

このコミットを理解するためには、以下の概念について知っておく必要があります。

  1. Unicode正規化 (Unicode Normalization): Unicodeには、同じ文字や文字シーケンスを表現する複数の方法が存在します。例えば、é という文字は、単一のコードポイント U+00E9 (LATIN SMALL LETTER E WITH ACUTE) で表現することもできますし、e (U+0065) と ´ (U+0301, COMBINING ACUTE ACCENT) の2つのコードポイントの組み合わせで表現することもできます。 このような複数の表現方法が存在すると、文字列の比較や検索において問題が生じる可能性があります。この問題を解決するために、Unicodeでは「正規化」というプロセスが定義されています。正規化は、特定のルールに基づいて文字列の表現を統一するものです。 主要な正規化形式には以下の4つがあります。

    • NFC (Normalization Form Canonical Composition): 結合文字を可能な限り合成して、最も短い形式にする正規化。例えば、e + ´é に合成されます。これはWebやファイルシステムで最も一般的に使用される形式です。
    • NFD (Normalization Form Canonical Decomposition): 合成された文字を可能な限り分解して、最も長い形式にする正規化。例えば、ée + ´ に分解されます。
    • NFKC (Normalization Form Compatibility Composition): 互換性文字(例えば、全角数字と半角数字)も考慮して合成する正規化。
    • NFKD (Normalization Form Compatibility Decomposition): 互換性文字も考慮して分解する正規化。
  2. FCD (Fast C/C++ Compatible Decomposition): FCDは、Unicodeの正規化形式の一つですが、NFCやNFDのように標準的な正規化形式として広く知られているものではありません。FCDは、ICUライブラリのような特定の文脈で、照合(collation)やその他のテキスト処理を効率的に行うために導入された概念です。 FCD形式の文字列は、その文字列をNFDに正規化した際に、結合文字の順序が変更されないという特性を持ちます。これは、照合アルゴリズムが文字列を分解して処理する際に、結合文字の順序が安定していることを保証するために重要です。NFC形式の文字列は、必ずしもFCD形式であるとは限りません。NFCは合成を優先するため、分解した際に結合文字の順序がFCDの要件を満たさない場合があります。

  3. ICU (International Components for Unicode): ICUは、Unicodeおよび国際化をサポートするための成熟したC/C++ライブラリのセットです。IBMによって開発され、オープンソースとして提供されています。文字列の照合(ソート)、日付・時刻のフォーマット、数値のフォーマット、テキストの境界検出(単語、行、文)、文字セット変換など、多岐にわたる国際化機能を提供します。多くのプログラミング言語やオペレーティングシステムで、国際化機能の基盤として利用されています。

  4. 照合 (Collation): 照合とは、文字列を特定の言語や文化のルールに従ってソート(並べ替え)するプロセスです。単純な辞書順ソートとは異なり、照合はアクセント記号、大文字・小文字、特殊文字、結合文字、さらには言語固有の文字の並び(例: スペイン語の chll)などを考慮に入れます。ICUやGoの exp/locale/collate パッケージは、このような複雑な照合ルールを実装しています。

技術的詳細

このコミットの技術的な核心は、Unicode正規化形式の選択が照合結果に与える影響と、ICUライブラリの内部的な要件への対応です。

Goの exp/locale/collate パッケージは、文字列の照合順序を決定するために、Unicode Collation Algorithm (UCA) に基づいています。UCAは、文字列を比較する前に、特定の正規化形式に変換することを推奨しています。通常、NFCが推奨されることが多いですが、照合の文脈ではNFDがより安定した結果をもたらす場合があります。

ICUライブラリは、その照合エンジンが効率的かつ正確に動作するために、入力文字列がFCD形式であることを内部的に要求します。FCDは、結合文字の順序が安定していることを保証する特性を持つため、照合キーの生成や比較の際に、予期せぬ順序の変更を防ぐことができます。

問題は、Goの colcmp ツールがこれまで文字列をNFC形式に正規化していた点にありました。NFCは合成を優先するため、一部の文字列では、NFC形式にしてもFCD形式の要件を満たさない場合があります。例えば、特定の結合文字の組み合わせは、NFCでは合成されますが、分解された際に結合文字の順序がFCDの要件に合致しないことがあります。このような文字列がICUに渡されると、ICUの内部処理とGoの collate パッケージの処理の間で不一致が生じ、比較結果が異なるという現象が発生していました。

このコミットでは、colcmp ツールが文字列をNFD形式に正規化するように変更することで、この問題を解決しています。NFDは、全ての合成文字をその構成要素に分解するため、結果として得られる文字列は常にFCD形式の要件を満たします。NFDに変換された文字列は、結合文字が常に分解された状態で存在し、その順序が安定しているため、ICUの照合エンジンが期待する形式と一致します。これにより、Goの collate パッケージとICUの間の照合結果の不一致が解消され、colcmp ツールがより正確な比較を行えるようになりました。

この変更は、exp/locale/collate パッケージ自体の照合ロジックには直接影響を与えませんが、そのテストと検証に使用されるツール colcmp の正確性を向上させるものです。これは、Goの国際化機能が業界標準と互換性を持つことを保証するための重要なステップと言えます。

コアとなるコードの変更箇所

変更は、src/pkg/exp/locale/collate/tools/colcmp/ ディレクトリ内の2つのファイルで行われています。

  1. src/pkg/exp/locale/collate/tools/colcmp/colcmp.go
  2. src/pkg/exp/locale/collate/tools/colcmp/gen.go

それぞれのファイルで、文字列の正規化形式をNFCからNFDに変更しています。

src/pkg/exp/locale/collate/tools/colcmp/colcmp.go の変更点:

--- a/src/pkg/exp/locale/collate/tools/colcmp/colcmp.go
+++ b/src/pkg/exp/locale/collate/tools/colcmp/colcmp.go
@@ -279,7 +279,7 @@ func parseInput(args []string) []Input {
 		}
 		s = string(rs)
 		if *doNorm {
-			s = norm.NFC.String(s)
+			s = norm.NFD.String(s)
 		}
 		input = append(input, makeInputString(s))
 	}

src/pkg/exp/locale/collate/tools/colcmp/gen.go の変更点:

--- a/src/pkg/exp/locale/collate/tools/colcmp/gen.go
+++ b/src/pkg/exp/locale/collate/tools/colcmp/gen.go
@@ -139,7 +139,7 @@ func (g *phraseGenerator) generate(doNorm bool) []Input {
 			buf16 = make([]uint16, 0, buf16Size)
 		}
 		if doNorm {
-			buf8 = norm.NFC.AppendString(buf8, str)
+			buf8 = norm.NFD.AppendString(buf8, str)
 		} else {
 			buf8 = append(buf8, str...)
 		}

コアとなるコードの解説

上記の変更箇所は、Go言語の golang.org/x/text/unicode/norm パッケージが提供する正規化機能を使用しています。

  • norm.NFC.String(s): 文字列 s をNFC形式に正規化し、新しい文字列を返します。
  • norm.NFD.String(s): 文字列 s をNFD形式に正規化し、新しい文字列を返します。
  • norm.NFC.AppendString(buf8, str): バイトスライス buf8 に文字列 str をNFC形式で正規化して追加します。
  • norm.NFD.AppendString(buf8, str): バイトスライス buf8 に文字列 str をNFD形式で正規化して追加します。

colcmp.goparseInput 関数は、入力された文字列を処理する部分です。*doNorm フラグが真の場合(つまり正規化が必要な場合)、これまでは norm.NFC.String(s) を使ってNFC正規化を行っていましたが、このコミットで norm.NFD.String(s) に変更されました。これにより、colcmp ツールが比較のために使用する入力文字列がNFD形式になります。

gen.gophraseGeneratorgenerate メソッドは、テスト用のフレーズを生成する部分です。ここでも doNorm フラグが真の場合に正規化が行われますが、同様に norm.NFC.AppendString から norm.NFD.AppendString へと変更されました。これにより、生成されるテストデータもNFD形式で正規化されることになります。

これらの変更により、colcmp ツールがICUとの比較を行う際に、ICUが期待するFCD形式(NFDはFCDの要件を満たす)の文字列を供給できるようになり、両者の照合結果の不一致が解消されました。

関連リンク

参考にした情報源リンク

  • コミットメッセージと差分情報
  • Unicode Standard Annex #15: Unicode Normalization Forms
  • ICU User Guide: Collation
  • Go言語の x/text リポジトリのドキュメントとソースコード
  • 一般的なUnicode正規化に関する技術記事とドキュメント