【Angular Utilities】NFD 文字を NFC に変換する Normalize To NFC を作った

主に MacOS の Finder なんかで問題になる、NFD 文字。濁点と半濁点が別の文字に別れており、Finder からファイル名をコピペしたりすると、「バイブス」という文字列が「ハ゛イフ゛ス」のようになってしまう、というものだ (この例は全角の濁点文字で再現)。こうなると、この文字列は「バイブス」と検索してもヒットしなくなってしまう。

ついでに、Windows などで通常どおり濁点・半濁点付きの文字になるのは NFC と呼ばれる。

ちょこちょこ MacOS でファイル名などをコピペすることがあるので、この NFD 濁点・NFD 半濁点を検知して、通常の濁点・半濁点付きの文字に置換するツールを作った。

問題になるのは、日本語においては濁点と半濁点が付く文字のみ。「ガバザダバパ」行と、「ヴ」だけである。そこで、ひらがなとカタカナでこれらの文字を表す文字コードを配列で持っておき、辞書的に利用することにした。

/** 清音文字の辞書 : 濁音文字の配置と揃えることで、同じ index 値で参照できるようにしておく */
const uncombinedKanaDict = [
  0x304B, 0x304D, 0x304F, 0x3051, 0x3053,  // かきくけこ
  0x3055, 0x3057, 0x3059, 0x305B, 0x305D,  // さしすせそ
  0x305F, 0x3061, 0x3064, 0x3066, 0x3068,  // たちつてと
  0x306F, 0x3072, 0x3075, 0x3078, 0x307B,  // はひふへほ (ば行用)
  0x306F, 0x3072, 0x3075, 0x3078, 0x307B,  // はひふへほ (ぱ行用)
  0x3046,                                  // う
  0x30AB, 0x30AD, 0x30AF, 0x30B1, 0x30B3,  // カキクケコ
  0x30B5, 0x30B7, 0x30B9, 0x30BB, 0x30BD,  // サシスセソ
  0x30BF, 0x30C1, 0x30C4, 0x30C6, 0x30C8,  // タチツテト
  0x30CF, 0x30D2, 0x30D5, 0x30D8, 0x30DB,  // ハヒフヘホ (バ行用)
  0x30CF, 0x30D2, 0x30D5, 0x30D8, 0x30DB,  // ハヒフヘホ (パ行用)
  0x30A6                                   // ウ
];
/** 濁音文字の辞書 : 清音文字の配置と揃えることで、同じ index 値で参照できるようにしておく */
const combinedKanaDict = [
  0x304C, 0x304E, 0x3050, 0x3052, 0x3054,  // がぎぐげご
  0x3056, 0x3058, 0x305A, 0x305C, 0x305E,  // ざじずぜぞ
  0x3060, 0x3062, 0x3065, 0x3067, 0x3069,  // だぢづでど
  0x3070, 0x3073, 0x3076, 0x3079, 0x307C,  // ばびぶべぼ
  0x3071, 0x3074, 0x3077, 0x307A, 0x307D,  // ぱぴぷぺぽ
  0x3094,                                  // ゔ
  0x30AC, 0x30AE, 0x30B0, 0x30B2, 0x30B4,  // ガギグゲゴ
  0x30B6, 0x30B8, 0x30BA, 0x30BC, 0x30BE,  // ザジズゼゾ
  0x30C0, 0x30C2, 0x30C5, 0x30C7, 0x30C9,  // ダヂヅデド
  0x30D0, 0x30D3, 0x30D6, 0x30D9, 0x30DC,  // バビブベボ
  0x30D1, 0x30D4, 0x30D7, 0x30DA, 0x30DD,  // パピプペポ
  0x30F4                                   // ヴ
];

NFD 文字になっている場合、たとえば「ザ」は「サ゛」という2文字になっている。入力文字列を1文字ずつ切り取って検証していく中で、

かつ、

に、uncombinedKanaDict のどこにこの文字があるか、findIndex() で添字を取得しておく。

変数 combinedKanaDict とは、清音と濁音の文字を配列の順番で対応付けてあるので、「サ」が uncombinedKanaDict[31] にあることが分かれば、「ザ」は combinedKanaDict[31] にあることが分かる。

このようにして、「清音文字 + NFD 濁点」の組み合わせから、通常の濁音文字を取得して結合しているのである。


ところで、NFD 用の濁点 (&#x3099) と半濁点 (&#x309A) は、通常の全角濁音 (&#x309B&#x309C) および半角濁音 (&#xFF9E&#xFF9F) とは別の文字で、手前の文字と合成して使うことを前提として構成されている。そのため、通常 Mac で文字列選択する際も、濁点だけ・半濁点だけを選択することができず、手前の文字が濁音のない文字であっても、変な形で強制的に合成表示されてしまう。

あまりないとは思うが、何かの拍子に「オ゜」のようにありえない濁音ができてしまった時に、単独の NFD 濁音文字を全角の濁音文字に変換するオプションも用意した。これなら、単独の濁音文字が混じっていてもコピペしたり扱いやすくなる。


ついでに、ngx-clipboard というモジュールを導入して、変換後の文字列をコピペしやすくした。

内部的には、よくある実装のとおり、textarea 要素を画面外に生成して、文字列をコピーしている。自分で実装するとなるとちょいと面倒になるので、こういう風にエコな使い回しができるのは良き良き。