「濁音に苦労した話」の続きである。元の稿に加筆したように、SpotifyのAPIで取れる曲情報でもcombining charactersが使われている(= 本体と(半)濁点が分離している)場合があることが分かった。
確かに、APIとそれ以外で別の情報を格納して別々に出すのは無駄だし、合理的でない。
表示は文字列が綺麗に出ればいいが、APIで取った曲情報は自作の音楽再生履歴記録システム MlhiのDBに格納しているので、今ひとつ良くない。というのは、今はまだ完全でない(イマイチな)ので余り使っていないが、将来は自分が聴いた曲をフレキシブルに(要するに、手軽に思ったとおりに)検索できるようにする予定で、その時に、combining charactersの濁音や半濁音を含む曲が出て来ない可能性があるからだ。
そういえば、以前、なぜか検索出来ない曲があったが、それだったのかも知れない。
(1/27 14:55) ちなみに、DBを調べたところ、曲情報(タイトル、アーティスト名、アルバム名、アルバムアーティスト)のいずれかにcombining characters(濁音、半濁音)が使われているものは、延べ27曲だった。DB全体は約1万曲なので、割合は約0.3%未満と少ない。ただ、僕は洋楽やクラシック音楽を聴くことが多く、曲情報が英語のものが多いから、日本の曲での割合は数%くらいではないか。
これを解決するには、以下の方法が考えられる。
- DBに正規化した曲情報を格納する。
- 今までにDBに格納した内容は正規化する。
- 検索時に、検索文字列とDBの内容を正規化して比較する。
- DBには、取得した情報をそのまま格納する。
- × 検索時に、検索文字列として、正規化したものとそうでないものを両方指定する。
- 正規化された文字を逆変換(分離)するのが面倒そうだが、uconvでできそうだ(本当?)。
- 曲情報に正規化されている文字と そうでないものが混ざっているので、検索パターンが多くなり過ぎる。
Bが筋が良さそうだが、結構面倒そうだ(そもそも、DBに使っているSQLiteはUnicodeのこういう処理に対応しているのだろうか??※)。一方、前者は やればできそうなので、そうすることにした。
※ sqlean: All the missing SQLite functionsというソフトがあり、それは限定的なUnicode処理機能(upper/lower, like, unaccent)を持つので、SQLite自体は対応していないようだ。
ここで問題になったことがある。: 前の稿に書いたように、uconvで正規化すると、かな に正規化漏れが起こるのと、漢字の旧字体が新字体に変換されてしまう。前者は自作の処理(テーブル変換)で対応できているが、後者に対応するには、日本語の文字(漢字)を正規化しない必要がある。※
※幸い、日本語の漢字には濁点のような着脱可能な要素が なさそうなので、正規化対象から除外するだけで良い。ただ、例えば「辶」(しんにょう)のように、場合によって個数が違う点のような要素はある(→ 興味深い読み物があった)。が、それは純粋に文字の形の問題と思われ、意味や読みは変わらないので別件としたい。
他には、たまに、富と冨ののような着脱可能っぽい点はあるが、それらは同じ文字らしいし、相互に入れ替えて意味を変えることはないので(しんにょうと同様に)ヨシ、だ。
そもそも、今までに、(遊びをのぞいてw)漢字に かな のように点や丸を付けて意味などを変えたことはないし、そういうことを教わったこともないから、それで良さそうだ。
UnicodeやICUに不慣れなので、日本語を除外して正規化することができるのか分からなかったが、いろいろ調べたら できそうなことが分かった。
Unicodeにはscriptという概念があり、Latin文字(いわゆる英字)、カタカナ、ひらがな、漢字※のようなカテゴリ(Unicodeにはcategoryという概念があるが、それではない)を示すことができる。それを使って、uconvの正規化ルール(日本語除外フィルタのようなもの)を指定すればできそうだ。
※実際には、Unicodeの資料では漢字は"Han"と書かれており、おそらく中・日・韓の漢字を一緒にしたもの(≒ CJK Unified Ideographs)を差していると理解している。
具体的には、以下のコマンドで日本語(正確には、漢字、ひらがな、カタカナ)以外の文字を正規化できるはずだ。
uconv -f utf-8 -t utf-8 -x '::[ [:^Katakana:] & [:^Hiragana:] & [:^Han:] ]; ::nfc;'
処理内容は、-xの最初のルール(最初の ; まで)でカタカナ、ひらがな、漢字を除外し、残ったものに正規化(NFC)を実行する("::nfc")。
実は、上の処理には欠陥がある。おそらく韓国語(ハングル文字)はcombining charactersをバリバリ使っているはずだが、それも除外されるのではないか(ハングル文字がHanに含まれているとした場合: 実際には、scriptには"Hangul"というのがあるから、ハングル文字はHanには含まれていなさそうだ)。
が、僕の好みからして、曲名などが韓国語で書かれた曲は聴かないだろうから、とりあえずは良しとする。あと、中国語には簡体字も繁体字もあるし(それは関係ないのかも知れないが)、どうなのか想像も付かないw
上の処理は、以下のテストパターンで うまく行った。
- パ が 神: 正規化されずに出た。
- Ā (A + U+0304: Combining Macron): Ā (U+0100: Latin Capital Letter A with Macron)になった。※
※Āを使ったのは たまたま最初に できた(macronなるものが付いた単体のĀがあった)からである。この文字がどういう意味なのか全く分からないし、実際に使われるのかも知らない。
と、出来そうになって気分がいいところで、「残りはあとで」にしたw
参考にしたページは以下である。
- ICU Documentation: General Transforms
- ICU Documentation: Transform Rule Tutorial
- Programming with Unicode: 3. Definitions
- Regular-Expressions.info: Unicode Regular Expressions
uconvに指定するルールの書き方は最初と2番目(特に後者)が参考になった。最後に残った謎(どうやって日本語を除外するか? そうするパターンをどうやって書くか?)を解く鍵になったのは、最後のUnicode Regular Expressionsだった。
それについては最初の2つを丹念に読めば分かったのだろうが、余りにも知らない概念が多いうえに機能が多過ぎてチンプンカンプンだった(→ 読む気が起こらなかった)。一方で、-Regular Expressionsは馴染みの正規表現の話なので良く分かった。※ 更に、Hanが かな を含むという思い違いに気付かせてくれて、上のルールが出来た。
※実際、このページを見付けた切っ掛けは、ICU(uconv)のルールが全然思ったように動かないので諦めて、grepで正規表現で除外しようと思い、「『漢字全部』の うまい指定」があればと思って"Unicode regexp"で検索したことだ。
ちなみに、grepは-PオプションでUnicodeに対応した動作(PCRE)になる。
PS. Unicode(ICU)の処理には、カタカナ→ローマ字みたいなものがあって(→ Script Transliterationの表の「キャンパス: kyanpasu」)、「誰が使うの?」と思う。ちゃんと動くのならいいが、正規化みたいに中途半端だったら実装するだけ無駄だし、ほとんどの開発者は知らないので、使われない気が・・・ (いやいや、僕が無知なだけだよね)
PS2. こういう細かい話は嫌いだけど好きだ。: 実装や確認は面倒でやりたくないけど、豆知識的に調べるのは好きだ。発展したのがタモリかw