読者です 読者をやめる 読者になる 読者になる

タイ語と標準Unicode属性

PHPの調べ物をしていたのに気付いたらRubyの記事を見ていました。
Rubyを使ってタイ語の表示文字単位で文字列を区切る - Qiita
これがなかなか面白くて、UnicodeやOnigmoについてちょろっと調べてみました。

元記事&コメント

Rubyを使ってタイ語の表示文字単位で文字列を区切る - Qiita
文字コードの範囲などは、コメント途中で正しいものが提示されていたので、それを反映した形で掲載しています)

タイ語の"พี่ชาย" (日本語で兄の意味)は表示上は4文字ですが、
最初の文字"พี่"が3つのUCS("e1e", "e35", "e48")から構成されており、
単純にsplit(//)をしただけでは6文字に分解されてしまいます。

コメント 前半

text = "พี่ชาย" # ["e1e", "e35", "e48", "e0a", "e32", "e22"]

ca = text.split(//).inject([]) do |r, ch|
  case ch
  when "\u0e31", "\u0e34".."\u0e3A", "\u0e47".."\u0e4e"
    r[-1] += ch
  else
    r << ch
  end
  r
end

これはまぁ、ふんふん、そうかという感じです。

コメント 正規表現

whenみたときに正規表現でできるかもなぁと思っていたところ
まさにそのコードが出てきました。

thai_str = "พี่ชาย"
thai_chars = thai_str.scan(/.(?:[\u0E31]|[\u0E33-\u0E3A]|[\u0E47-\u0E4E])*/) 

コメント 分割しない文字とOnigmo の \X eXtended grapheme cluster

znzさんのコメントが理解できませんでした。

"พี่ชาย".split(/(?!\p{Mn})/) のように分割しない文字を否定先読みで指定して split するのはどうでしょうか?
\p{Mn} は適当なので、そこは調整が必要ですが。

\p{Mn} なんやこれ。

ruby 2.0.0 から使える Onigmo の \X eXtended grapheme cluster がこういう用途にぴったりに見えました。
ruby 1.9.x なら https://github.com/k-takata/Onigmo/blob/master/doc/RE.ja などの説明にあるように (?>\P{M}\p{M}*) で代用出来ます。

お?

\p{Mn} とは

"พี่ชาย".split(/(?!\p{Mn})/)
=> ["พี่", "", "", ""]

確かに意図した結果が返ります。

Unicode属性の指定(\p{})

意味不明なので調べると 7. 正規表現の世界 - もうカツ丼でいいよな
以下抜粋。

7.2.1 Unicode属性 - Unicode文字には属性という情報がある。 - 正規表現では特定の文字だけでなく文字の種類にもマッチさせられる。属性名を\p{}で指定する。特定のUnicode属性をもたない文字にマッチさせることもできる。\P{}はその属性の否定となる。 - 空白文字:Space - 数字:Digit - 16進数:AHex

なるほど。Unicode属性を指定していて、大文字のPは否定なんですね。

Unicode属性(Mn)

続いてMnが表すのは 正規表現デスクトップリファレンス - トニー スタッブルバイン - Google ブックス
より抜粋すると \p{Mn} | 他の文字を修飾するための文字(アクセント、ウムラウト等)

ということは

(?!\p{Mn}) は、
「他の文字を修飾するための文字(アクセント、ウムラウト等)」を否定先読みしているってことですね。

Onigmo の \X eXtended grapheme cluster

またしても、正しい結果が返ります。
でもUnicode属性は先ほど抑えたのでそれほど怖くありません。

"พี่ชาย".scan(/(?>\P{M}\p{M}*)/)
=> ["พี่", "", "", ""]

?>

(?>pattern) | 原子的式集合
K.Takata's software: bregonig.dll
だそうです。

\P{M}\p{M}*

先ほどのgoogle bookさんを見ると
\p{M} | 基本文字と組み合わせて用いる記号(アクセント記号等)
とあります。
 
ということは、1文字目がこれに該当せず、それ以降がこれに該当する文字なので
今回の用途にぴったりなようです。

\X

そして コメントにリンクのある以下を見ると \X が省略記法のようです。
Onigmo/RE.ja at master · k-takata/Onigmo

"พี่ชาย".scan(/\X/)
=> ["พี่", "", "", ""]

おおすげー。
というかUnicode複雑すぎる。タイ人大変だな。