LoginSignup
0
0

Unicode 正規化と文字操作

Last updated at Posted at 2023-11-03

Unicode 正規化を文字操作に応用します。

合成文字はデータ数と文字数が一致しないなど Unicode の複雑性として言及されることが多いですが、便利な一面もあります。

Unicode 正規化

Unicode には文字の構成要素をまとめて 1 つのコードで表す NFC と、構成要素を別々にコード化する NFD という正規化方式があります。

※ その他 NFKC と NFKD もありますが、この記事では扱いません。

正規化を処理する API として、Python には unicodedata.normalize があります。

濁点

「が」という平仮名を例に説明します。NFC ではまとめて 1 つのコードで表しますが、NFD では「か」と「゛」に分解したコードで表します。

>>> import unicodedata
>>> def tohex(s):
...     return " ".join([f"{ord(ch):04x}" for ch in s])
...
>>> tohex(unicodedata.normalize("NFC", ""))
'304c'
>>> tohex(unicodedata.normalize("NFD", ""))
'304b 3099'
>>> chr(0x304b)
''
>>> chr(0x3099)
''

"\uXXXX" という指定方法もあります。今後はこちらを使用します。

>>> "\u304b"
''

分解すると len が文字数とは一致しなくなることに注意が必要です。

>>> len("")
1
>>> len(unicodedata.normalize("NFD", ""))
2

unicodedata.name で Unicode 規格での文字の名前が確認できます。

>>> unicodedata.name("\u304c")
'HIRAGANA LETTER GA'
>>> unicodedata.name("\u304b")
'HIRAGANA LETTER KA'
>>> unicodedata.name("\u3099")
'COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK'

※ 合成に使われる付加記号類には COMBINING で始まる名前が付けられます。結合文字と呼ばれます。

unicodedata.decomposition でどのように分解されるか確認できます。「か」はこれ以上分解できないため空文字列が返されます。

>>> unicodedata.decomposition("")
''
>>> unicodedata.decomposition("")
'304B 3099'

※ 文字コードが文字列で返されるため、先ほど定義した tohex は不要です。

複数の付加記号

アルファベットには複数の付加記号(アクセント類)が付くことがあり、NFD では記号ごとに分解されます。

>>> tohex(unicodedata.normalize("NFD", ""))
'0063 0327 0301'
>>> unicodedata.name("")
'LATIN SMALL LETTER C WITH CEDILLA AND ACUTE'
>>> unicodedata.name("\u0063")
'LATIN SMALL LETTER C'
>>> unicodedata.name("\u0327")
'COMBINING CEDILLA'
>>> unicodedata.name("\u0301")
'COMBINING ACUTE ACCENT'

decomposition は 2 段階で定義されます。normalize ではこれを一度に処理します。

>>> unicodedata.decomposition("")
'00E7 0301'
>>> "\u00e7"
'ç'
>>> unicodedata.decomposition("ç")
'0063 0327'
>>> unicodedata.name("ç")
'LATIN SMALL LETTER C WITH CEDILLA'

この場合、付加記号の順番を変えても NFC での合成に影響はありません。

>>> tohex(unicodedata.normalize("NFC", "\u0063\u0327\u0301"))
'1e09'
>>> tohex(unicodedata.normalize("NFC", "\u0063\u0301\u0327"))
'1e09'

付加記号の並びで異なる文字を表すこともあります。その組み合わせが規格で定義されているかに依存するため、規則性はありません。

>>> tohex(unicodedata.normalize("NFD", "ǖ"))
'0075 0308 0304'
>>> tohex(unicodedata.normalize("NFD", ""))
'0075 0304 0308'
>>> unicodedata.name("\u0075")
'LATIN SMALL LETTER U'
>>> unicodedata.name("\u0308")
'COMBINING DIAERESIS'
>>> unicodedata.name("\u0304")
'COMBINING MACRON'

ある記号がどのような文字と組み合わせられるかは、Unicode 規格書や Wikipedia で確認できます。

文字操作

通常の利用ではデータ量が抑えられる NFC 正規化が有利です。

文字の操作には NFD が有用です。いくつか例を示します。

濁点を付ける

濁点を表す付加記号を付けるだけです。

>>> def dakuten(ch):
...     return ch + "\u3099"
...
>>> dakuten("")
'ざ'
>>> dakuten("")
'ヅ'

このままだとデータ量が大きくなってしまいます。

>>> len(dakuten(""))
2
>>> tohex(dakuten(""))
'3055 3099'

return の前に NFC で合成した方が良いでしょう。

>>> def dakuten(ch):
...     return unicodedata.normalize("NFC", ch + "\u3099")
...
>>> tohex(dakuten(""))
'3056'

濁点を付加すると文字コードが 1 つ増えるだけに見えますが、例外はあります。

>>> tohex(""), tohex(dakuten(""))
('304b', '304c')
>>> tohex(""), tohex(dakuten(""))
('306f', '3070')
>>> tohex(""), tohex(dakuten(""))
('3046', '3094')

一般的に、ある記号を付けたときの文字コードの変化には規則性がありません。正規化をライブラリに任せれば、文字コードの変化を知らなくても処理が書けるので便利です。

濁点を取り除く

NFD で分解して最初の 1 文字だけを残せば、一律に濁点が取り除けます。

>>> def strip(text):
...     return "".join([unicodedata.normalize("NFD", ch)[0] for ch in text])
...
>>> strip("がぎぐげご")
'かきくけこ'

転写

ある文字から別の文字に書き換えるのにも利用できます。(転写)

古代ギリシア語は付加記号類が多く、アルファベットに転写するため Unicode 正規化を利用しました。

>>> import greektrans
>>> greektrans.romanize("Πάτερ ἡμῶν ὁ ἐν τοῖς οὐρανοῖς·")
'Páter hēmôn ho en tóès ūranóès;'

基本的には濁点を付ける例と同じことをやっています。当初は NFD で分解せずに NFC 正規化された状態で処理していたため、変換部分はすべてテーブルに依存していました。NFD で分解して扱うことによって、コードで付加記号を明示的に記述できるようになりました。記号の変更にも対応しやすいです。

転写の詳細はこちらを参照してください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0