Unicodeプロパティを使ったPerl正規表現

こんにちは、Webアプリケーションエンジニアのid:nanto_viです。

Webアプリケーションを作っていると、「全角文字と半角文字を統一したい」「ユーザーの入力から漢字を抜き出したい」といったテキスト処理を行う場面にたびたび遭遇します。はてなではWebアプリケーションのサーバー側プログラミング言語としてPerlを多く使っていますが、PerlならこのようなときにUnicodeプロパティを用いた正規表現パターンで柔軟な処理が可能です。

Unicodeプロパティ

現在、ほとんどのプラットフォームで採用されている文字集合がUnicodeです。Unicodeでは文字だけでなくその文字の様々な特性(プロパティ)も定められており、テキスト処理の基礎情報として活用できます。

Perl正規表現でのUnicodeプロパティの利用

Perlの正規表現では、\p{Property_Name=Value}のようにメタ文字\pを使うことで、あるプロパティ(Property_Name)が特定の値(Value)をとるような文字にマッチさせられます。ここで=の代わりに:を使うこともできます。また、\P{...}のように大文字\Pを使うことでマッチの成否を反転させられます。

代表的なUnicodeプロパティ

実際に利用できるプロパティには以下のようなものがあります。

General_Category

「表音文字・表語文字(Letter)」「数字(Number)」「記号(Symbol)」といった一般的な文字の分類を表します。「表音文字・表語文字」は「大文字(Uppercase_Letter)」「小文字(Lowercase_Letter)」……など、より細かく分けられます。

'A'  =~ m/\p{General_Category=Letter}/;           # 「A」は表音文字・表語文字
'あ' =~ m/\p{General_Category=Letter}/;           # 「あ」も表音文字・表語文字
'A'  =~ m/\p{General_Category=Uppercase_Letter}/; # 「A」は大文字
'あ' =~ m/\p{General_Category=Other_Letter}/;     # 「あ」はその他の文字

Script

「ラテン文字(Latin)」「キリル文字(Cyrillic)」「平仮名(Hiragana)」「漢字(Han)」など、その文字がどのような表記体系で使われるかを表します。これは言語とは別のくくりです。たとえば日本語は一般に「平仮名」「片仮名」「漢字」を使って表記されますが、「ラテン文字」を使って表記する(ローマ字表記)ことも可能です。

文字によっては複数の表記体系で使われることもあります。たとえば日本語の長音符「ー」(U+30FC)は平仮名表記でも片仮名表記でも同じ文字が使われます。このように複数の表記体系で使われる文字のScriptはCommonとなります。

'ア' =~ m/\p{Script=Katakana}/; # 「ア」は片仮名
'ー' !~ m/\p{Script=Katakana}/; # 「ー」は片仮名としてのみ使われるわけではない
'ー' =~ m/\p{Script=Common}/;   # 「ー」は複数の表記体系で共通に使われる

Script_Extensions

Scriptプロパティは1文字につきひとつの値しか割り当てられず、複数の表記体系で使われる文字はCommon / Inheritedに押し込められています。そのような文字に対しても使われうる表記体系を列挙したのがScript_Extensionsプロパティであり、その値は1文字につき複数存在します。

長音符「ー」(U+30FC)のScript_Extensionsの値はHiragana / Katakanaのふたつになります。ただしUnicode 8.0の時点で「㍿」(U+337F)のScript_ExtensionsがBopomofo / Hangul / Han / Hiragana / Katakanaであるなど、直感的でない値の割り当てもあります。

'ー' =~ m/\p{Script_Extensions=Hiragana}/; # 「ー」は平仮名としても使われる
'ー' =~ m/\p{Script_Extensions=Katakana}/; # 「ー」は片仮名としても使われる

Block

Blockは連続する符号位置をまとめた領域です。Scriptと同じ名前のBlockも存在しますが、両者に含まれる文字が同じであるとは限りません。たとえばU+3097Hiraganaブロックに含まれますが、この符号位置に文字は割り当てられていません。漢字はCJK Unified IdeographsCJK Compatibility IdeographsCJK Unified Ideographs Extension Aなど複数のブロックに格納されています。

Blockはあくまでもその文字の符号位置による分類(U+0U+10FFFFまでの符号位置を適宜切り分けたもの)であり、文字種を調べるのには不適切です。文字種に応じた処理にはScriptまたはScript_Extensionsプロパティを見るようにしましょう。

'ー' =~ m/\p{Block=Katakana}/; # 「ー」の符号位置はKatakanaブロックに含まれる
'ー' !~ m/\p{Block=Hiragana}/; # 「ー」は平仮名としても使われるが、その符号位置はHiraganaブロックに含まれない

East_Asian_Width

その文字がいわゆる全角幅なのか半角幅なのかを表します。文脈によって表示幅が異なる場合は値Ambiguousを取ります。詳しくは「東アジアの文字幅 - Wikipedia」などを参照してください。

'あ' =~ m/\p{East_Asian_Width=Wide}/;      # 「あ」は全角幅で扱われる。
'☆' =~ m/\p{East_Asian_Width=Ambiguous}/; # 「☆」は東アジアでは全角幅で、他地域では半角幅で扱われることがある。

ID_Start / ID_Continue

プログラミング言語などの識別子に使える文字であるかどうかを表します。値として真(True)か偽(False)かを取ります。実際にこのプロパティに応じて識別子を解釈するかはプログラミング言語次第ですが、JavaScriptはこの定義を採用しており、1文字目がID_Start$_、2文字目以降がID_Continue$_U+200CU+200Dである識別子を変数名や関数名として使えます。

'JSの妥当な識別子' =~ m/
    \A
    [\p{ID_Start=True}\$_]
    [\p{ID_Continue=True}\$_\x{200C}\x{200D}]*
    \z
/x;

省略記法

上述したプロパティの名前と値には、それぞれ省略表記が存在します。General_Categoryの値にはアルファベット1~2文字の省略表記、ScriptおよびScript_Extensionsの値にはアルファベット4文字の省略表記も使えます。

'あ' =~ m/\p{gc=Lo}/;           # "General_Category=Other_Letter"の省略表記
'ア' =~ m/\p{sc=Kana}/;         # "Script=Katakana"の省略表記
'字' =~ m/\p{scx=Hani}/;        # "Script_Extensions=Han"の省略表記
'A'  =~ m/\p{blk=Basic_Latin}/; # "Block=Basic_Latin"の省略表記
'¥' =~ m/\p{ea=F}/;            # "East_Asian_Width=Fullwidth"の省略表記

値が真偽値を取るようなプロパティなら、値自体を省略できます。その場合は値に真が指定されたものとみなされます。

'あ' =~ m/\p{ID_Start}/; # "ID_Start=True"の省略表記

General_CategoryおよびScriptに関しては、プロパティ名を省略できます。

'A'  =~ m/\p{Uppercase_Letter}/; # "General_Category=Uppercase_Letter"の省略表記
'a'  =~ m/\p{Ll}/;               # "General_Category=Lowercase_Letter"の省略表記
'あ' =~ m/\p{Hiragana}/;         # "Script=Hiragana"の省略表記

他のプログラミング言語での対応

Perl以外の言語でも正規表現パターンで\pメタ文字を使えることがあります。ただし、Script名にIs接頭辞をつける、Block名にIn接頭辞をつけるなど、言語によってプロパティの指定方法が微妙に変わってきます。

  • Ruby
    • General_Category、Script、Block、真偽値をとるプロパティの指定ができます。
  • Java
    • General_Category、Script、Block、真偽値をとるいくつかのプロパティの指定ができます。
  • .NET
    • General_Category、Blockの指定ができます。
  • JavaScript

参考

終わりに

以上で紹介したようなUnicodeプロパティを使うことで、より柔軟なテキスト処理が可能になります。はてなでは多様なカテゴリのエンジニアを募集しています。

hatenacorp.jp

また、はてなでは今夏もサマーインターンシップが開催されます。Web開発に興味のある学生の方はぜひ応募してみてください。はてなでのひと夏があなたをつくります。

「はてなサマーインターン2016申し込み」はこちら