正規表現クラス の変更点 - アールメカブ

アールメカブ


正規表現クラス の変更点


以下は http://module.jp/blog/regex_unicode_prop.html より引用

今時の正規表現ではUnicodeブロックやUnicodeスクリプトを指定して、サクッと日本語の特定文字種にマッチさせることができるのですが、毎回忘れるんです。その覚え書き。
2月売りのWEB+DB Pressの正規表現ネタ原稿がようやく終わりました。(い)さん遅くなってごめんなさいです。今回はPerlで「今時フツーの正規表現」を解説し、 Javaのjava.util.regexパッケージ、PHPのpreg系関数とereg系関数を書きまして、あと別の方に.NETの正規表現のネタを書いて頂きました。PHP以外はいずれもUnicode対応した正規表現エンジンなので、

\p{Prop}

といった形式でUnicodeにおける文字の特性(プロパティ)、言語別の書記体系(スクリプト)、文字コード上の範囲(ブロック)を指定することができます。Unicodeプロパティの簡単な例をいくつか挙げると

\p{L}	表示可能な文字
\p{M}	他の文字と組み合わせて使用する文字
\p{Z}	区切り文字
\p{S}	記号
\p{N}	数字
\p{P}	句読点
\p{C}	L,M,Z,S,N,P以外のすべての文字
\p{Ll}	小文字
\p{Lu}	大文字
\p{Lo}	ヘブライ語や日本語などの大文字と小文字を持たない文字。
\p{Sc}	通貨記号

とか。Unicodeスクリプトの場合日本語に絡んでくるのは

\p{Katakana}	カタカナ
\p{Hiragana}	ひらがな
\p{Han}	漢字
\p{Latin}	英数字
\p{Common}	スペースや句読点など

など。ただしUnicodeスクリプトまでサポートしている正規表現エンジンはPerlのぐらい。最後にUnicodeブロックで日本語に絡んでくるのは

\p{InHiragana}	ひらがな
\p{InKatakana}	カタカナ
\p{InHalfwidthAndFullwidthForms}	半角カタカナ
\p{InCJKUnifiedIdeographs}	漢字

といった感じ。ちなみにjava.util.regexとPerlのUnicodeブロックは接頭子Inを使うが、.NETの場合は接頭子Isを使う、という差異があります。

Unicodeスクリプトとブロックの違いがビミョーに見えるけど、ブロックがコードブロックをゴリッと指定したものに対して、スクリプトは特定言語に関係する文字の種類を直接指定するものなのでブロックよりも断定的、って感じで見れば良かなと。ちなみにUnicode関連のドキュメントによると Unicodeプロパティとスクリプトで日本語の文章を表そうとすると

m/(?:(?:\p{Hiragana}|\p{Katakana}|\p{Han}|\p{Latin}|\p{Common})
  (?:\p{Inherited}|\p{Me}|\p{Mn})?)+/x;

こんな感じになるそうな。実際流通している文章はこれより多様なので現実とは微妙に乖離していますがだいたいこんな感じではあります。

これでなにが嬉しいかというと、文字種ごとにモリモリ文字列を切り分けようとする場合には単純に

import java.util.regex.*;

public class WordSplit {
    public static void main(String[] args) {
        String text = "漢字カナ混じりの文章をinputします";
        Pattern p = Pattern.compile(
            "(\\p{InBasicLatin}+|" +
            " \\p{InHiragana}+|" +
            " \\p{InKatakana}+|" +
            " \\p{InCJKUnifiedIdeographs}+)", Pattern.COMMENTS);
        Matcher m = p.matcher(text);
    
        while (m.find()) {
            String chunk = m.group(1);
            System.out.println(chunk);
        }
    }
}

とか

#!/usr/bin/perl

use Encode qw/encode decode/;
use strict;

my $text = decode('EUC-JP', '漢字カナ混じりのビミョーな文章をinputします');
my $pat = qr/(
    \p{InBasicLatin}+                 |
    \p{InHalfwidthAndFullwidthForms}+ |
    \p{Hiragana}+                     |
    \p{Katakana}+                     |
    \p{Han}+
)/x;

while ($text =~ /$pat/g) {
    print encode('EUC-JP', $1), "\n";
}
__END__

とか書くだけで済んだりします。ちなみにこれらを実行すると

漢字
カナ

じりの
ビミョー

文章

input
します

という出力を得ることができます。

で、問題はこれらをサポートしていないPHPで、以前のPerlのように使用するエンコーディングにあわせて文字種ごとのコードブロックを指定してあげる必要があります。だいたいこんな感じ。

<?php
$text = '漢字カナ混じりの文章をinputします';  // EUC-JP
$pattern = '/( 
    [\x21-\x7E]+                 | # Latin-1
   #(?:\x8E[\xA6-\xDF])+         | # HankakuKatakana
    (?:\xA4[\xA1-\xF3])+         | # Hiragana
    (?:\xA5[\xA1-\xF6])+         | # Katakana
    (?:[\xB0-\xF4][\x00-\xFF])+    # Kanji
)/x';

if (preg_match_all($pattern, $text, $capture)) {
    echo join("\n", $capture[1]), "\n";
}
?>

PHPはApache 2でも利用されているPCREライブラリのおかげ、java.util.regexは中の人のおかげで、かなりの部分で最近のPerlライクな正規表現が利用できます。