🦔

【PowerShell】濁音・半濁音と Unicode 正規化

2024/07/13に公開

macOS のファイル名のなかには NFC・NFD 形式の濁音、半濁音の文字が混在していることがある。NFC では1つのコードポイント、NFD では基底文字と結合文字の2つのコードポイントで構成される。Unicode 正規化には Normalize を使う。

"が".Normalize([Text.NormalizationForm]::FormD).Length
2
> "か`u{3099}".Normalize([Text.NormalizationForm]::FormC).Length
1

かなや濁点・半濁点は基本多言語面の範囲(U+000..U+FFFF)にあるので、文字数を Length でカウントしたが、一般的な文字数を数えるには EnumerateRunes か 後述するGetTextElementEnumerator を使う

"🐙と🦑".Length                                      
5
"🐙と🦑".EnumerateRunes().Value.Length 
3

Unicode 正規化で文字がどのように置き換わったのか確認してみる

echo|
>> %{ $_.Normalize([Text.NormalizationForm]::FormD) } |        
>> %{ $_.EnumerateRunes().Value } |
>> %{ "U+{0:X}" -f $_ }
U+304B
U+3099

NFD 形式の濁音・半濁音が混在していることを想定して文字列を処理するのであれば、正規表現の文字クラスを使って適用範囲を制限する。ひらがなもしくはカタカナと濁点もしくは半濁点で構成される書記素クラスターの正規表現は [\p{IsHiragana}\p{IsKatakana}]\p{Mn} であらわすことができる。

-replace 演算子を使って置き換える例を示す

> $str = "は`u{3099}ハ`u{3099}と神"
> $str.length                      
6

> $ret = $str -replace '[\p{IsHiragana}\p{IsKatakana}]\p{Mn}', `
{ $_.Value.Normalize([Text.NormalizationForm]::FormC) }

> $ret.length           
4

書記素クラスターの数を数えるには LengthInTextElements を使う

echo がぎぐ |
>> %{ $_.Normalize([Text.NormalizationForm]::FormD) } | 
>> %{ ([Globalization.StringInfo] $_).LengthInTextElements }
3

書記素クラスター単位で何らかの処理を行うには GetTextElementEnumerator を使う

echo がぎぐ |
>> %{ $_.Normalize([Text.NormalizationForm]::FormD) } | 
>> %{ [Globalization.StringInfo]::GetTextElementEnumerator($_) }
が
ぎ
ぐ

書記素クラスターが2つのコードポイントで構成されており、2つめのコードポイントの値が 0x3099 もしくは 0x309A である場合に Unicode 正規化を適用すればよい

Discussion