LoginSignup
33
20

More than 3 years have passed since last update.

[Python] 結合文字を使用した濁点や半濁点を直前の仮名と結合させる方法(ウ゛→ ヴ)

Last updated at Posted at 2019-09-23

まえがき

人名検索が行えるデータベースを作る際、提供されたデータにこんな読み仮名が混在していた。

  • ヤマグチ
  • ヤマク゛チ

ヤマグチと検索してもヤマク゛チさんがヒットしないので、「ク゛」を「」に寄せる形で正規化することにした。
適当なライブラリが見当たらなかったので、自前で実装することにした奮闘記。

実装方法

  1. 文字列内の合成文字を基底文字と結合文字へ分解
  2. 1で分解した文字列をバイト列に変換
  3. 2で変換したバイト列の濁点、半濁点をUnicode結合文字の濁点、半濁点に置換
  4. 3で置換されたバイト列を文字列型へ戻す
  5. (必要に応じて)合成文字に変換

Unicodeの合成文字と結合文字

日本語の「」「」「」のように符号の有無・種類で音を表現する文字に対し、Unicodeでは合成文字結合文字を使用する二通りの表現方法がある。

「ば」の例では以下の二種類がある。

  • U+3070
  • + 濁点 U+306F + U+3099

合成文字基底文字、濁点が結合文字である。

濁点や半濁点結合文字へ置換してやればUnicode上、結合された1文字となる

Wikipediaにわかりやすい例があるので引用する。

例: â は U+00E2 (latin small letter a with circumflex) でも、U+0061 U+0302 (latin small letter a + combining circumflex accent) でも表すことができる。
結合文字 - Wikipedia

UTF-8の合成文字と結合文字

Unicode上での合成文字と結合文字の動作がわかったところで、Unicodeに対応した文字符号化方式で一番メジャーであろうUTF-8で合成文字と結合文字の動きを見る。

合成文字

import unicodedata

print(unicodedata.normalize("NFC", "ヤ マ グ チ").encode())
> b'\xe3\x83\xa4 \xe3\x83\x9e \xe3\x82\xb0 \xe3\x83\x81'

"ヤマグチ"のが、\xe3\x82\xb0符号化されている

結合文字

import unicodedata

print(unicodedata.normalize("NFD", "ヤ マ グ チ").encode())
> b'\xe3\x83\xa4 \xe3\x83\x9e \xe3\x82\xaf\xe3\x82\x99 \xe3\x83\x81'

"ヤマグチ"のが、\xe3\x82\xaf\xe3\x82\x99の組み合わせで符号化されている

濁点・半濁点の文字符号

UnicodeとUTF-8でそれぞれ対応する符号を知る必要があるが、Wikipediaにあった。

濁点 - Wikipedia
半濁点 - Wikipedia

記号 Unicode UTF-8符号 備考
U+309B \xe3\x82\x9b 全角濁点
- U+3099 \xe3\x82\x99 濁点(結合文字)
U+FF9E \xef\xbe\x9e 半角濁点
U+309C \xe3\x82\x9c 全角半濁点
- U+309A \xe3\x82\x9a 半濁点(結合文字)
U+FF9F \xef\xbe\x9f 半角半濁点

全角濁点半角濁点濁点(結合文字)へ置換、
全角半濁点半角半濁点半濁点(結合文字)へ置換する

コード

#!/usr/bin/env python3

import re
import unicodedata

def join_diacritic(text, mode="NFC"):
    """
    基底文字と濁点・半濁点を結合
    """
    # str -> bytes
    bytes_text = text.encode()

    # 濁点Unicode結合文字置換
    bytes_text = re.sub(b"\xe3\x82\x9b", b'\xe3\x82\x99', bytes_text)
    bytes_text = re.sub(b"\xef\xbe\x9e", b'\xe3\x82\x99', bytes_text)

    # 半濁点Unicode結合文字置換
    bytes_text = re.sub(b"\xe3\x82\x9c", b'\xe3\x82\x9a', bytes_text)
    bytes_text = re.sub(b"\xef\xbe\x9f", b'\xe3\x82\x9a', bytes_text)

    # bytet -> str
    text = bytes_text.decode()

    # 正規化
    text = unicodedata.normalize(mode, text)

    return text

実行
join_diacritic("ルイズ・フランソワーズ・ル・ブラン・ド・ラ・ウ゛ァリエール")
> ルイズフランソワーズブランヴァリエール

応用

ヴァリエールちゃんをウ゛ァリエールちゃんにしたい。

    # 一度結合文字へ変換して
    text = unicodedata.normalize("NFD", text)

    # 結合文字を全角濁点・半濁点へ変換すればできる
    bytes_text = re.sub(b'\xe3\x82\x99', b"\xe3\x82\x9b", bytes_text)
    bytes_text = re.sub(b"\xe3\x82\x9a", b'\xe3\x82\x9c', bytes_text)

UTF-8以外の文字符号化方式に対応する

    # 例えばこの様に実装にすれば、UTF-8以外にも対応できる
    bytes_text = re.sub("\u309B".encode(charset), "\u3099".encode(charset), bytes_text)
    bytes_text = re.sub("\uFF9E".encode(charset), "\u3099".encode(charset), bytes_text)

おわりに

Unicode結合文字の仕組み使うので、Shift_JISとかのお友達は一度Unicodeに準じた文字符号化方式にエンコードすることで使えるようになる(たぶん):tired_face:

33
20
2

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
33
20