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; }
実行結果
こんにちは、世界