ICU 2.4 の正規表現
IBMのOpenSource: ICU はUnicodeを扱うライブラリとして高い評価を得ています。ICUは何度かのバージョンアップを繰り返しながら、その度にライブラリとしての完成度を上げてきました。
ICU 2.4では、新たにUnicode文字列を対象とした正規表現(regular Expression)をサポートします。正規表現を扱うアプリケーションの構築にはBoost Regex++などのライブラリを使用していましたが、ICUが正規表現をサポートしたことで、選択の幅がより広がったことになります。
ICUの正規表現を利用する場合は、少なくともライブラリ: icuuc と icuin をリンクしてください。
なお、以下のサンプルは Microsoft Visual C++ 6.0 および 7.0 でコンパイル/実行を確認しました。
ICUが定義した型UCharとwchar_tが等価であることを前提としており、そうでない場合はUnicodeString str = L"..."; のような代入ができません。
RegexPatternとRegexMatcher
ICUの正規表現は、<unicode/regex.h>に定義された二つのクラス:RegexPatternとRegexMatcherによってサポートされます。RegexPatternは正規表現文字列を内部形式にコンパイルしたもの、RegexMatcherはRegexPatternに基づいた正規表現パターンが文字列のどの位置に現れるかを検索します。この二つはJava2 1.4で導入された java.util.regex パッケージの Pattern, Matcher とコンパチブルなインタフェースを提供しています。
パターン生成
RegexPatternのインスタンスはstaticメソッド:
RegexPattern* RegexPattern::compile(const UnicodeString& regex, UParseError& pe, UErrorCode& status);
によって生成します。regexに正規表現文字列を与えます。
コンパイルに失敗したときはstatusにエラーコード、peにエラーの詳細情報が格納されます。
UnicodeString regex = L"(.)や"; // 正規表現 UParseError pe; UErrorCode status = U_ZERO_ERROR; RegexPattern* pattern = RegexPattern::compile(regex, pe, status); assert( U_SUCCESS(status) );
生成されたパターンからRegexMatcherを生成するのがRegexPatternのメソッド:
RegexMatcher* RegexPattern::matcher(const UnicodeString& input, UErrorCode& status);
です。inputに検索対象文字列を与えます。
UnicodeString input = L"松島やああ竹島や梅島や"; // 入力(検索対象) RegexMatcher* matcher = pattern->matcher(input, status); assert( U_SUCCESS(status) );
RegexPatternは、正規表現にマッチする部分を単語の区切りと見なしてトークン分割する
int32_t RegexPattern::split(const UnicodeString& input, UnicodeString dest[], UErrorCode& status);
を持っています。inputを単語毎に切り出し、dest[destCapacity]に格納し、単語数を返します。
RegexPatternによるトークン分割
#include <iostream>
#include <locale>
#include <cassert>
#include <unicode/regex.h>
int main() {
std::locale::global(std::locale("ja"));
UParseError error;
UErrorCode status = U_ZERO_ERROR;
UnicodeString regex = L"[とやも]";
RegexPattern* pattern = RegexPattern::compile(regex, error, status);
assert( U_SUCCESS(status) );
UnicodeString fruits[10];
UnicodeString input = L"リンゴとミカンやナシとモモも";
int32_t splits = pattern->split(input, fruits, 10, status);
assert( U_SUCCESS(status) );
for ( int32_t i = 0; i < splits; ++i ) {
std::wcout << fruits[i].getTerminatedBuffer() << std::endl;
}
delete pattern;
return 0;
}
実行結果
リンゴ ミカン ナシ モモ
検索
RegexMatcher::find()を呼び出す毎に、 inputからregexとマッチする部分文字列を順に検索します(マッチしなかったとき、0を返します)。
find() の結果は:
int32_t RegexMatcher::start(int group, UErrorCode& status),
int32_t RegexMatcher::end(int group, UErrorCode& status)
で得られます。
それぞれ、マッチ個所の先頭/末尾の位置を返します。 groupには正規表現に現れるグルーピング(括弧で囲まれた部分)を与えることができ、0あるいは省略時には正規表現全体を意味します。
また、UnicodeString RegexMatcher::group(int groupNum, UErrorCode& status) は、マッチする部分文字列を返します。
matcher->reset();
while ( matcher->find() ) {
for ( int32_t i = 0; i <= matcher->groupCount(); ++i ) {
std::wcout << L"[" << matcher->start(i,status)
<< L"," << matcher->end(i,status)
<< L")" << matcher->group(i,status).getTerminatedBuffer()
<< std::endl;
assert( U_SUCCESS(status) );
}
std::wcout << std::endl;
}
置換
UnicodeString RegexMatcher::replaceFirst(const UnicdeString& replacement, UErrorCode& status)
によって、最初に現れたマッチ個所をreplacementに置き換えた文字列を返します。
マッチ個所すべてを置換するときは:
UnicodeString RegexMatcher::replaceAll(const UnicdeString& replacement, UErrorCode& status)
を用います。replaceAllは以下のコードと等価です:
UnicodeString result;
matcher->reset();
while ( matcher->find() ) {
matcher->appendReplacement(result, replacement, status);
assert( U_SUCCESS(status) );
}
matcher->appendTail(result);
残念ながら、グルーピングたとえば正規表現 "(.)や" の "(.)" に対応する部分(部分マッチ)を置換するメソッドを提供していないようです。
部分マッチの置換は、検索とUnicodeString::replaceを用いた以下のような実装となるでしょう。
#include <iostream>
#include <locale>
#include <cassert>
#include <vector>
#include <utility>
#include <unicode/regex.h>
int main() {
std::locale::global(std::locale("ja"));
UParseError error;
UErrorCode status = U_ZERO_ERROR;
UnicodeString regex = L"(.)島や";
RegexPattern* pattern = RegexPattern::compile(regex, error, status);
assert( U_SUCCESS(status) );
UnicodeString input = L"松島やああ竹島や梅島や";
RegexMatcher* matcher = pattern->matcher(input, status);
assert( U_SUCCESS(status) );
/*
* 部分マッチ"(.)"の置換
*/
typedef std::pair<int32_t,int32_t> range_type;
std::vector<range_type> matches;
matcher->reset();
while ( matcher->find() ) {
// 第一グループを保存する
matches.push_back(range_type(matcher->start(1,status),
matcher->end(1,status)));
assert( U_SUCCESS(status) );
}
UnicodeString replacement = L"松";
UnicodeString result = input;
// 末尾から置換
while ( !matches.empty() ) {
range_type range = matches.back();
matches.pop_back();
result.replace(range.first, range.second - range.first,
replacement);
}
std::wcout << result.getTerminatedBuffer() << std::endl;
delete matcher;
delete pattern;
return 0;
}
サンプル
#include <iostream>
#include <locale>
#include <cassert>
#include <unicode/regex.h>
int main() {
std::locale::global(std::locale("ja"));
UParseError error;
UErrorCode status = U_ZERO_ERROR;
UnicodeString regex = L"(.)島や";
RegexPattern* pattern = RegexPattern::compile(regex, error, status);
assert( U_SUCCESS(status) );
UnicodeString input = L"松島やああ竹島や梅島や";
RegexMatcher* matcher = pattern->matcher(input, status);
assert( U_SUCCESS(status) );
/*
* 検索
*/
matcher->reset();
while ( matcher->find() ) {
for ( int32_t i = 0; i <= matcher->groupCount(); ++i ) {
std::wcout << L"[" << matcher->start(i,status)
<< L"," << matcher->end(i,status)
<< L")" << matcher->group(i,status).getTerminatedBuffer()
<< std::endl;
assert( U_SUCCESS(status) );
}
std::wcout << std::endl;
}
UnicodeString replacement = L"松島や";
UnicodeString result;
/*
* 置換 : replaceFirst
*/
result = matcher->replaceFirst(replacement, status);
assert( U_SUCCESS(status) );
std::wcout << result.getTerminatedBuffer() << std::endl;
/*
* 置換 : replaceAll
*/
result = matcher->replaceAll(replacement, status);
assert( U_SUCCESS(status) );
std::wcout << result.getTerminatedBuffer() << std::endl;
/*
* 置換 : appendReplacement/appendAll
*/
result = L"";
matcher->reset();
while ( matcher->find() ) {
matcher->appendReplacement(result, replacement, status);
assert( U_SUCCESS(status) );
}
matcher->appendTail(result);
std::wcout << result.getTerminatedBuffer() << std::endl;
delete matcher;
delete pattern;
return 0;
}
実行結果
[0,3)松島や [0,1)松 [5,8)竹島や [5,6)竹 [8,11)梅島や [8,9)梅 松島やああ竹島や梅島や 松島やああ松島や松島や 松島やああ松島や松島や
※ コンパイラ/OSによっては std::wcoutが漢字やかなを正常に出力できないものがあります。
そのような場合は、以下のコードを付加し、std::cout << narrow(...); してください。
このとき、コード冒頭の#include <locale> および std::locale::global(std::locale("ja");は不要です。
UnicodeString -> std::string 変換
#include <iostream>
#include <string>
#include <unicode/unistr.h>
std::string narrow(const UnicodeString& str) {
// マルチバイト文字は最大2-charと仮定する。
// shift_jis/euc-jp ならこれで大丈夫。
int32_t length = str.length();
char* buffer = new char[length*2+1];
str.extract(0, length, buffer, "shift_jis");
std::string result(buffer);
delete[] buffer;
return result;
}
/*
* お試し
*/
int main() {
UnicodeString input = L"こんにちは、世界";
std::cout << narrow(input) << std::endl;
return 0;
}
実行結果
こんにちは、世界