Unicodeでの正規表現


例えば、ユニコード型文字列の内、漢字のみ("一"〜"龥")をマッチさせる場合

l = re.findall(u"[\u4e00-\u9fa5]+", "ひらがな漢字カタカナascii".decode("utf-8"))
print l[0].encode("utf-8") # -> "漢字"

他の言語と違ってUnicode型というデータ型があるので明瞭極まりない!


PythonはわからないけどPerlに通じるものを感じたのでPerlでやってみた。


まずは上記のコードに似せたパターン。スクリプトファイルはUTF-8で作成。

use strict;
use warnings;
use Encode;

my @l = decode('utf8', 'ひらがな漢字カタカナascii') =~ /[\x{4e00}-\x{9fa5}]+/g;
print encode('utf8', "@l");
漢字

decode()とencode()の部分はたぶんPythonと同じ。
Perlの場合、内部表現としてのUTF-8文字列と単なるバイト列であるUTF-8文字列は区別される*1ので、まずは内部表現に変換してやる。
つまり、たまたまUTF-8であるにすぎないただのバイト列から、PerlUnicodeとして認識しうるものに変換する。考え方はPythonと同じっぽいね。


そして、スクリプトUTF-8で書く場合にもう少しよさそうな方法。

use utf8;
#use open qw(:utf8 :std);
use open ':locale';
use strict;
use warnings;
use Encode;

my @l = 'ひらがな漢字カタカナascii今日はいい天気' =~ /[\x{4e00}-\x{9fa5}]+/g;
print @l;
漢字

use openについては正直どうするのがいいのかわからないのでさておくとして、use utf8すると、スクリプトUTF-8で書かれていると見なされる。
これによって文字列リテラルには自動的にUTF-8 Flagが付くし、ついでに変数名とか関数名もUTF-8で書けるようになる。日本語の変数名とか。
というわけで、当初のコードからdecode()がなくなった。
encode()がなくなったのはuse openの影響で、use utf8は関係ない。
ちなみに:locale指定は入出力をロケールに合わせてよしなに処理してくれるのだがWindowsだとダメだった気がする。


というわけで、「他の言語」に反応してみた。
Rubyはわからないけど、きっとできるんだろう。
PHP? シラネヽ(´ー`)ノ

おまけ

コードレンジだけでなくスクリプト名やブロック名でも試してみる。

use utf8;
use strict;
use warnings;
use Encode;

my @props = qw(
	4E00-9FA5
	Han
	InCJKUnifiedIdeographs
	InCJKSymbolsAndPunctuation
	Hiragana
	InHiragana
	Katakana
	InKatakana
	InHalfwidthAndFullwidthForms
	FF64-FF9F
	Latin
);

my @patterns;
foreach my $prop (@props) {
	if ($prop =~ /^[\da-f]+-[\da-f]+$/i) {
		push @patterns, sprintf('[\x{%s}-\x{%s}]+', split('-', $prop));
	} else {
		push @patterns, "\\p{$prop}+";
	}
}

local $| = 1;

# use utf8;
print "[flagged utf8:unicode string]\n";
my $str = '123@〒【】ひらがな漢字カタカナハンカクカナascii';
print '$str is utf8? ', Encode::is_utf8($str) ? 'yes' : 'no', "\n";
print "\$str: $str\n";  # warning
print '$str length: ', length($str), "\n";
foreach my $pattern (@patterns) {
	my @matches = $str =~ /($pattern)/g;
	printf("%-34s: %s\n", $pattern, encode_utf8(join(', ', @matches) || '(no match)'));
}

print "\n[unflagged utf8:bytes string]\n";
my $bstr = encode_utf8($str);
print '$bstr is utf8? ', Encode::is_utf8($bstr) ? 'yes' : 'no', "\n";
print "\$bstr: $bstr\n";
print '$bstr length: ', length($bstr), "\n";
foreach my $pattern (@patterns) {
	my @matches = $bstr =~ /($pattern)/g;
	printf("%-34s: %s\n", $pattern, join(', ', @matches) || '(no match)');
}
[unicode string]
$str is utf8? yes
Wide character in print at unicode_match.pl line 35.
$str: 123@〒【】ひらがな漢字カタカナハンカクカナascii
$str length: 28
[\x{4E00}-\x{9FA5}]+              : 漢字
\p{Han}+                          : 漢字
\p{InCJKUnifiedIdeographs}+       : 漢字
\p{InCJKSymbolsAndPunctuation}+   : 〒【】
\p{Hiragana}+                     : ひらがな
\p{InHiragana}+                   : ひらがな
\p{Katakana}+                     : カタカナハンカクカナ
\p{InKatakana}+                   : カタカナ
\p{InHalfwidthAndFullwidthForms}+ : 123@, ハンカクカナ
[\x{FF64}-\x{FF9F}]+              : ハンカクカナ
\p{Latin}+                        : ascii

[bytes string]
$bstr is utf8? no
$bstr: 123@〒【】ひらがな漢字カタカナハンカクカナascii
$bstr length: 74
[\x{4E00}-\x{9FA5}]+              : (no match)
\p{Han}+                          : (no match)
\p{InCJKUnifiedIdeographs}+       : (no match)
\p{InCJKSymbolsAndPunctuation}+   : (no match)
\p{Hiragana}+                     : (no match)
\p{InHiragana}+                   : (no match)
\p{Katakana}+                     : (no match)
\p{InKatakana}+                   : (no match)
\p{InHalfwidthAndFullwidthForms}+ : (no match)
[\x{FF64}-\x{FF9F}]+              : (no match)
\p{Latin}+                        : �, �, �, �, �, �, �, �, �, �, �, ��, �, �, �, �, �, �, �, �, �, �, �, ascii


なぜ半角カナを示す定義がないのかと小一時間問い詰めたい
"Wide character in print at unicode_match.pl line 35."はUTF-8 Flagのついた文字列をそのまま出力しようとしてるので怒られている様子。
そして最後のやつは文字化けしまくり。

*1:UTF-8 Flagというもので管理されている