JavaScript

String.prototype.normalize()メソッド

 編集:2022.08.16 by T.A.

その String を Unicode 正規化形式で処理した文字列を返します。

構文
string= String.normalize( [form] )
String
form 省略可。Unicode 正規化形式を示す "NFC"、"NFD"、"NFKC"、"NFKD" のうちの一つです。省略されたり undefined であったりした場合は "NFC" が使われます。

これらの値には以下の意味があります:

"NFC" 正規化形式 C。正準等価性によって分解され、再度合成される。
"NFD" 正規化形式 D。正準等価性によって分解される。
"NFKC" 正規化形式 KC。互換等価性によって分解され、正準等価性によって再度合成される。
"NFKD" 正規化形式 KD。互換等価性によって分解される。
戻り値

String。与えられた文字列の Unicode 正規化形式を含む文字列です。

例外
RangeError
form が上記で指定された値のいずれでもない場合は RangeError が発生します。
解説

Unicode は個々の文字に対して、コード ポイントと呼ばれる固有の値を割り当てています。例えば、"A" のコードポイントは U+0041 が割り当てられています。しかし、複数のコード ポイントや、コード ポイントの一連の並びが、同一の抽象文字を表すことがあります。― 例えば、"ñ" の文字は以下のいずれかで表すことができます。※ U+300~036F:ダイアクリティカルマーク(合成可能)

let string1='\u00F1';
D=string1;  //="ñ"

let string2='\u006E\u0303';
D=string2;  //="ñ"

しかし、コード ポイントが異なるため、文字列の比較ではこれらが同じものとして扱われません。また、それぞれのコード ポイントの数が異なるため、長さすら異なります。

let string1='\u00F1';       //="ñ"
let string2='\u006E\u0303'; //="ñ"

D=string1 === string2; //=false
D=string1.length;      //=1
D=string2.length;      //=2

normalize() メソッドは、同じ文字を表すコード ポイントのすべての並びを共通の正規化された形式に文字列を変換することで、この問題を解決するのに役立ちます。正規化の方法は主に二つがあり、一つは正準等価性に、もう一つは互換等価性に基づきます。

正準等価性による正規化

Unicode では、二つのコード ポイントの並びが同じ抽象文字を表していれば、正準等価性があるとされ、常に同じ外見表示と動作をするべきです(例えば、並べ替えで常に同じものとして扱うべきです)。

normalize() を "NFD" または "NFC" の引数で使用することで、すべてが正準等価な文字列となる文字列の形を生成することができます。
以下の例では、文字 "ñ" の二つの表現を正規化しています:

let string1 = '\u00F1';           // ñ
let string2 = '\u006E\u0303';     // ñ

string1 = string1.normalize('NFD');
string2 = string2.normalize('NFD');

D=string1 === string2; //=true
D=string1.length;      //=2
D=string2.length;      //=2
合成形と分解形

"NFD" で正規化された形の長さが 2 であることに注意してください。"NFD" は分解正規形を生成するからであり、これは単一のコード ポイントを複数のコード ポイントの組み合わせに分解します。"ñ" の分解正規形は "\u006E\u0303" です。

"NFC" を指定すると合成正規形を取得することができ、これは複数のコード ポイントを可能な限り単一のコード ポイントで置き換えます。"ñ" の合成正規形は "\u00F1" です。

let string1 = '\u00F1';       //="ñ"
let string2 = '\u006E\u0303'; //="ñ"

string1 = string1.normalize('NFC');
string2 = string2.normalize('NFC');

D=string1 === string2;                 //=true
D=string1.length;                      //=1
D=string2.length;                      //=1
D=string2.codePointAt(0).toString(16)); //=f1。(16進表記に変換)
互換正規形

Unicode では、二つのコード ポイントの並びが、同じ抽象文字を表す場合に互換性があり、場合によっては同じ文字として扱われるべきですが、すべてのアプリケーションでそうするべきとは限らないことがあります。

すべての正準等価な並びは互換といえますが、逆はそうとはいえません。

例を挙げます。

場面によっては(並べ替えなど)同じものとしてみなされるべきであり、その他の場合は(外見など)同じとするべきではないので、これらは厳密には等しくありません。

normalize() を "NFKD" または "NFKC" を引数にして使用することで、互換等価な文字列が同じになる形の文字列を生成することができます。

let string1 = '\uFB00';
let string2 = '\u0066\u0066';

D=string1;             //="ff"
D=string2;             //="ff"
D=string1 === string2; //=false
D=string1.length;      //=1
D=string2.length;      //=2

string1 = string1.normalize('NFKD');
string2 = string2.normalize('NFKD');

D=string1;             //="ff"。外見が変わった
D=string2;             //="ff"
D=string1 === string2; //=true
D=string1.length;      //=2
D=string2.length;      //=2

互換等価な正規化を適用する際には、正規化された形式がすべてのアプリケーションに適しているとは限らないので、文字列で何をしようとしているのかを考慮することが重要です。上の例では、ユーザーが "f" を検索すれば文字列を見つけることができるので、正規化は検索に適しています。しかし、視覚的な表現が異なるため、表示には適切ではないかもしれません。

正準等価な正規化のように、分解形または合成形の互換等価形式は、それぞれ "NFKD" または "NFKC" を渡すことで問い合わせることができます。

normalize() を使う
// 最初の文字列
// U+1E9B: LATIN SMALL LETTER LONG S WITH DOT ABOVE
// U+0323: COMBINING DOT BELOW
let str='\u1E9B\u0323'; //="ẛ̣"

// Canonically-composed form (NFC)
// U+1E9B: LATIN SMALL LETTER LONG S WITH DOT ABOVE
// U+0323: COMBINING DOT BELOW
D=str.normalize('NFC'); //="ẛ̣"
D=str.normalize();      //=同上

// Canonically-decomposed form (NFD)
// U+017F: LATIN SMALL LETTER LONG S
// U+0323: COMBINING DOT BELOW
// U+0307: COMBINING DOT ABOVE
D=str.normalize('NFD'); //="ẛ̣"。("\u017F\u0323\u0307")

// Compatibly-composed (NFKC)
// U+1E69: LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE
D=str.normalize('NFKC'); //="ṩ"。("\u1E69")

// Compatibly-decomposed (NFKD)
// U+0073: LATIN SMALL LETTER S
// U+0323: COMBINING DOT BELOW
// U+0307: COMBINING DOT ABOVE
D=str.normalize('NFKD'); //="ṩ"。("\u0073\u0323\u0307")

テスト