株式会社エス・スリー・フォー

正規表現ライブラリ ‘Boost Regex++’ の使い方

‘webを介したプログラムの実行’というアプリケーションの新しい形態が現れ、perl,pythonなどに代表されるスクリプト言語がよく利用されるようになりました。スクリプト言語の多くはテキストすなわち文字列を扱うのを得意としています。

一方C++は文字列を扱うのがあまり得意ではありません。 最近になってようやく文字列を表現するクラスstd::basic_string が標準ライブラリに組み入れられました。 これによって文字列操作を頻繁に行い、かつ移植性の高いアプリケーションの実装がとてもやりやすくなりました。

しかしながら標準の文字列を手に入れたC++でもperlなどのスクリプト言語にかなわないことの一つが’正規表現(regular expression)’です。

たとえばWebの自動巡回を行うアプリケーションを考えてみましょう。
ひとつのHTMLからその中に書かれたリンク情報(URL)を抽出し、
URLが示すリンク先をたどって…というような処理を行います。
このときまず必要なのは’HTMLからリンク情報(URL)を抽出する’ことです。

HTML内のリンク情報は: <a href='http://www.s34.co.jp/cpptechdoc/index.html'>のような書式で表されます。

  1. “<a”
  2. 1個以上の空白
  3. 引用符
  4. 任意の文字列(ここがURL)
  5. 引用符
  6. 0個以上の空白
  7. “>”

という順序で現れる文字列パターンをHTMLすなわち文字列のなかから探し出さなければなりません。
これを標準C関数やstd::basic_stringのメソッドだけで実現しようとすると、
そのコードはかなりの大きさになることでしょう。

この文字列パターンの正規表現は: <a +href=('|\").*('|\") *> となります。
正規表現ライブラリは文字列から正規表現にマッチ(適合)する部分を探し出してくれます。

C-インタフェースを提供する正規表現ライブラリはいくつかあるのですが、C++で使い勝手がよく、
かつ高機能/高性能のものは非常に少ないようです。
その数少ないC++版正規表現ライブラリのひとつ、Regex++の使い方を解説します。

※Regex++はフリーのC++ライブラリ’Boost’に収録されており、http://www.boost.org/から入手できます。

なお、このアーティクルではRegex++の基本的な使用法の解説にとどめます。 詳しくはライブラリに添付のドキュメントを参照してください。

regex_search

const char* source = 文字列;
boost::reg_expression<char> regex = 正規表現;
boost::match_results<const char*> results;

bool found = boost::regex_search(source, results, regex);

sourceからregexにマッチする部分文字列を探します。マッチする部分文字列が見つかればtrue, 見つからなければfalseを返します。

マッチする部分文字列が見つかったとき、resultsにその詳細が納められます。

resutls.str(n)は、regexにあるn番目の'(‘とそれに対応する’)’に囲まれた部分にマッチする部分文字列(std::string)を返します。たとえば:

const char* source = "abc1234def";
boost::reg_expression<char> regex = "([a-zA-Z])(.*)[a-zA-Z]";
boost::match_results<const char*> results;
boost::regex_search(source, results, regex);

では、

result.str(1) = "c"
result.str(2) = "1234"

となります。 なお、str(0)
regex全体とマッチする部分文字列すなわち"c1234d"です。

results.position(n),
results.length(n)

は、それぞれn番目のカッコに対応する部分文字列の位置と長さを返します。上の例では:

results.position(1) = 2
results.length(1) = 1

results.position(2) = 3
results.length(2) = 4

漢字(2バイト文字)を含むマルチバイト文字列に対しては正しい結果が得られません。

たとえばShift_JIS環境で:

const char* soure = "アルファベットを含むShift_JIS文字列";
boost::reg_expression<char> regex = "([a-zA-Z])(.*)[a-zA-Z]";
boost::match_results<const char*> results;
boost::regex_search(source, results, regex);

この実行結果は、results.str(0) = "Aルファベットを含むShift_JIS" となってしまいます。
Shift_JISでは’ア’の第2バイトが’A’と一致するからです。

マルチバイト文字列は一旦Unicodeに変換しなければなりません。
UnicodeであればRegex++で正しい結果を得ることができます:

const wchar_t* soure = L"アルファベットを含むShift_JIS文字列";
boost::reg_expression<wchar_t> regex = L"([a-zA-Z])(.*)[a-zA-Z]";
boost::match_results<const wchar_t*> results;
boost::regex_search(source, results, regex);
std::wstring found =  results.str(0); // found = L"Shift_JIS"

regex_match

regex_searchがマッチする部分文字列を検索するのに対し、
regex_matchは処理の対象である文字列全体が与えられた正規表現と完全にマッチしたときtrueを返します。

// 入力文字列が時刻表現(hh:mm:ss)か?
std::string input;
std::cin >> input;
boost::reg_expression<char> regex = "([0-9][0-9]):([0-9][0-9]):([0-9][0-9])";
boost::match_results<std::string::iterator> results;
if ( boost::regex_match(source, results, regex) ) {
  std::cout << results.str(1) << "hr. " << results.str(2) << "min. " << results.str(3) << "sec.\n";
} else {
  std::cout << "invalid input\n";
}

regex_grep

regex_searchは与えられた文字列から、正規表現に最初にマッチする箇所を探します。
これに対しregex_grepは与えられた文字列から正規表現にマッチする箇所を’すべて’列挙します。

unsigned int n = boost::regex_grep(predicate, source, regex);

第2,第3引数はregex_search,regex_matchと同様、それぞれ検索対象文字列、正規表現です。

第1引数predicatematch_resultsのconst参照を引数に取り、bool値を返す関数オブジェクトです。
正規表現にマッチする箇所が見つかる度に呼び出されます。検索を続行するならtrueを、停止するならfalseを返してください。regex_greppredicateを呼び出した回数を返します。

struct print_submatch {
  bool operator ()(const boost::match_results<const std::wstring::const_iterator>& m) {
  std::wcout << m.str(0) << std::endl;
  return true;
  }
};

const std::wstring source = L"私は山が好きで海が好きで川も好きです。";
boost::reg_expression<wchar_t> regex = L"(山|川).好き";
boost::regex_grep(print_submatch(), source, regex);

regex_search, regex_grep
のサンプルコードを以下に示します。 あなたの文字列操作にお役立てください。

regexpp.cpp
#include <iostream>
#include <locale>

#include <boost/regex.hpp>

using std::cout;
using std::wcout;
using std::endl;

void bad_search() {
  cout << "\nRegex++ can NOT support multibyte regex" << endl;
  std::string source = "アルファベットを含むShift_JIS文字列";
  boost::reg_expression<char> regex = "[a-zA-Z].*[a-zA-Z]";
  boost::match_results<std::string::const_iterator> results;
  if ( boost::regex_search(source, results, regex) ) {
    cout << results.str(0) << endl;
  }
}

void good_search() {
  cout << "\nso let's try std::wstring & Regex++" << endl;
  std::wstring source = L"アルファベットを含むShift_JIS文字列";
  boost::reg_expression<wchar_t> regex = L"[a-zA-Z].*[a-zA-Z]";
  boost::match_results<std::wstring::const_iterator> results;
  if ( boost::regex_search(source, results, regex) ) {
    wcout << results.str(0) << endl;
  }
}

void test_search() {

  cout << "\nregex_search for const char*" << endl;
  {
    const char* source = "<a href='http://www.roguewave.com:80/index.html'>here</a>";
    boost::reg_expression<char> regex  = "(([a-z]+):)?//([^:/]+)(:([0-9]+))?/([a-zA-Z.0-9]*)";
    boost::match_results<const char*> results;
    cout << "URL = " << source << endl
         << "regex = " << regex.str() << endl;
    if ( boost::regex_search(source, results, regex) ) {
      cout << " scheme :" << results.str(2) << endl
           << " host   :" << results.str(3) << endl
           << " port   :" << results.str(5) << endl
           << " path   :" << results.str(6) << endl;
    } else {
      cout << "no match." << endl;
    }
  }

  cout << "\nregex_search for std::string" << endl;
  {
    std::string source = "<a href='http://www.roguewave.com:80/index.html'>here</a>";
    boost::reg_expression<std::string::value_type> regex  = "(([a-z]+):)?//([^:/]+)(:([0-9]+))?/([a-zA-Z.0-9]*)";
    boost::match_results<std::string::const_iterator> results;
    cout << "URL = " << source << endl
         << "regex = " << regex.str() << endl;
    if ( boost::regex_search(source, results, regex) ) {
      cout << " scheme :" << results.str(2) << endl
           << " host   :" << results.str(3) << endl
           << " port   :" << results.str(5) << endl
           << " path   :" << results.str(6) << endl;
    } else {
      cout << "no match." << endl;
    }
  }

}

void test_wide_search() {

  cout << "\nregex_search for const wchar_t*" << endl;
  {
    const wchar_t* source = L"<a href='http://www.roguewave.com:80/index.html'>here</a>";
    boost::reg_expression<wchar_t>  regex  = L"(([a-z]+):)?//([^:/]+)(:([0-9]+))?/([a-zA-Z.0-9]*)";
    boost::match_results<const wchar_t*> results;
    wcout << L"URL = " << source << endl
          << L"regex = " << regex.str() << endl;
    if ( boost::regex_search(source, results, regex) ) {
      wcout << L" scheme :" << results.str(2) << endl
            << L" host   :" << results.str(3) << endl
            << L" port   :" << results.str(5) << endl
            << L" path   :" << results.str(6) << endl;
    } else {
      cout << "no match." << endl;
    }
  }

  cout << "\nregex_search for std::wstring" << endl;
  {
    std::wstring source = L"<a href='http://www.roguewave.com:80/index.html'>here</a>";
    boost::reg_expression<std::wstring::value_type> regex  = L"(([a-z]+):)?//([^:/]+)(:([0-9]+))?/([a-zA-Z.0-9]*)";
    boost::match_results<std::wstring::const_iterator> results;
    wcout << L"URL = " << source << endl
          << L"regex = " << regex.str() << endl;
    if ( boost::regex_search(source, results, regex) ) {
      wcout << L" scheme :" << results.str(2) << endl
            << L" host   :" << results.str(3) << endl
            << L" port   :" << results.str(5) << endl
            << L" path   :" << results.str(6) << endl;
    } else {
      cout << "no match." << endl;
    }
  }
}

struct grep_predicate {
  bool operator()(const boost::match_results<std::string::const_iterator>& m) {
    cout << m.str(0) << endl;    return true;
  }
};

void test_grep() {
  cout << "\nregex_grep for std::string\n";
  const std::string             source   = "My name is same as a name of a month";
  boost::reg_expression<char> regex    = ".ame";
  boost::regex_grep(grep_predicate(), source.begin(), source.end(), regex);
}

struct grep_wide_predicate {
  bool operator()(const boost::match_results<std::wstring::const_iterator>& m) {
    wcout << m.str(0) << endl;
    return true;
  }
};

void test_wide_grep() {
  cout << "\nregex_grep for std::wstring\n";
  const std::wstring             source   = L"僕は海が好きで山が好きで川も好き";
  boost::reg_expression<wchar_t> regex    = L"(山|川)(.)好き";
  boost::regex_grep(grep_wide_predicate(), source.begin(), source.end(), regex);
}

int main() {
  std::locale::global(std::locale("japanese"));

  bad_search();
  good_search();

  test_search();
  test_wide_search();
  test_grep();
  test_wide_grep();

  return 0;
}
実行結果
Regex++ can NOT support multibyte regex
Aルファベットを含むShift_JIS

so let's try std::wstring & Regex++
Shift_JIS

regex_search for const char*
URL = <a href='http://www.roguewave.com:80/index.html'>here</a>
regex = (([a-z]+):)?//([^:/]+)(:([0-9]+))?/([a-zA-Z.0-9]*)
 scheme :http
 host   :www.roguewave.com
 port   :80
 path   :index.html

regex_search for std::string
URL = <a href='http://www.roguewave.com:80/index.html'>here</a>
regex = (([a-z]+):)?//([^:/]+)(:([0-9]+))?/([a-zA-Z.0-9]*)
 scheme :http
 host   :www.roguewave.com
 port   :80
 path   :index.html

regex_search for const wchar_t*
URL = <a href='http://www.roguewave.com:80/index.html'>here</a>
regex = (([a-z]+):)?//([^:/]+)(:([0-9]+))?/([a-zA-Z.0-9]*)
 scheme :http
 host   :www.roguewave.com
 port   :80
 path   :index.html

regex_search for std::wstring
URL = <a href='http://www.roguewave.com:80/index.html'>here</a>
regex = (([a-z]+):)?//([^:/]+)(:([0-9]+))?/([a-zA-Z.0-9]*)
 scheme :http
 host   :www.roguewave.com
 port   :80
 path   :index.html

regex_grep for std::string
name
same
name

regex_grep for std::wstring
山が好き
川も好き