LoginSignup
1
0

More than 3 years have passed since last update.

std::regex を独自イテレータに適用する

Last updated at Posted at 2021-02-01

std::regex を独自イテレータに適用する場合の可用性について考察しました。

要約

  • wchar_t を使えば、 Windows と Linux でOKでした
  • char はマルチバイト文字を一文字と認識しませんでした

考察

std::regex は、ユニコードへの対応を求められていません。 http://www.akenotsuki.com/misc/srell/uniregex.html によると、 C++ 標準化委員会は、 std::regex のユニコード対応に後ろ向きなようです。

std::regexは、char8_tchar16_tchar32_tをサポートしません。std::regexは、charwchar_tをサポートします。

charの文字コードはロケールに依存します。 wchar_tの文字コードは環境固有です。

wchar_t

std::regex はユニコード対応でないが、たいていの環境でwchar_tがユニコードなので、wchar_tを使えばユニコード対応になりそうです。

wchar_tの文字コードを Linux と Windowsで調べました。

UTF-16 と UCS4 があるので、環境ごとの場合分けが必要そうです。

char

システムのロケールを調べました。システムデフォルトのロケールは LANG 環境変数、ユーザーのロケールは LC_* 環境変数で設定されます。Windows で POSIX スタイルのロケール設定を調べる方法はわかりませんでした。

  • 手元の Debian10
    • LANG=en_US.UTF-8
    • LC_CTYPE="en_US.UTF-8"
  • 手元の Ubuntu20.04
    • LANG=C.UTF-8
    • LC_CTYPE="C.UTF-8"

でした。

イテレータ

std::regex は双方向イテレータしか要求しないため、独自イテレータの実装は容易です。 双方向イテレータはマルチパス保証を要求するため、実際にコンテナが保持するデータを指すのが現実的です。 デコードによって別の文字コードを公開するのは現実的でありません。

実験

wchar_t

文字を走査するイテレータ・アダプタを使って、 HTML 文書 Tree を正規表現検索してみました。

実行した環境は以下の通りです。

  • Ubuntu 20.04 gcc9.3.0 libstdc++6
  • Windows10 2004 Visualstudio2019 16.8.4 clang-cl
  • Windows10 2004 Visualstudio2019 16.8.4 cl
using namespace wordring;
using namespace wordring::html;

// Windows と Linux で wchar_t の文字コードが違うため、宣言を切り替えます。
#ifdef __linux__
    using tree_type = u32simple_tree;
#else
    using tree_type = u16simple_tree;
#endif // __linux__

// HTML 文書 Tree を作成します。
std::string_view sv = u8"<span>ABCあ</span><a>い</a><strong>うDEF</strong>";
auto doc = make_document<tree_type>(sv.begin(), sv.end());

// char16_t あるいは char32_t を wchar_t に見せかけるイテレータ・アダプタの型を宣言します。
using wchar_iterator = tree_type::wchar_iterator;
// wchar_t へのキャストが付いた「文字を走査するイテレータ・アダプタ」を作成します。
auto it1 = wchar_iterator(doc.begin());
auto it2 = wchar_iterator(doc.end());

// 正規表現検索します。
std::basic_regex<wchar_t> rx(L"あ.う");
std::match_results<wchar_iterator> mr;
std::regex_search(it1, it2, mr, rx);

// アサーションは成功します。
assert(mr.length() == 3);
assert(mr.str() == L"あいう");

以上のコードで

  • make_document() は、 HTML 文書を返す HTML パーサーの便利関数
  • u8simple_tree は、 HTML 文書を格納する Tree
  • wchar_iterator は、 「文字を走査するイテレータ・アダプタ」の逆参照を強制的にwchar_t へキャストするイテレータ・アダプタ

です。

wchar_tchar16_t あるいは char32_t ではありません。 したがって、char16_t を指すイテレータを wchar_t として逆参照すると、エラーとなります。(※clではエラーとなりませんでした。)そこで、 逆参照を強制的にキャストして返すイテレータ・アダプタを試作しました。 これは完全に動作しました。

character_iterator は単なるアダプタですから、 base となる HTML ノードへのイテレータへ変換可能です。 上記のコードに続けて、正規表現とマッチした最初の範囲の親要素のタグ名を表示します。

// マッチした最初の範囲を示す文字イテレータを取得し、 HTML ノードへのイテレータへキャストします。
tree_type::iterator it3 = mr.begin()->first;
// ノードへのイテレータから親のタグ名を取り出し文字コードを UTF-16 へ変換します。
auto tag = encoding_cast<std::u16string>(it3.parent()->local_name());

// アサーションは成功します。
assert(tag == u"span");

以上のコードで、

  • encoding_cast() は、文字エンコーディングを変換する便利関数

です。

これが独自イテレータの旨味です。 std::string へ変換してから検索したのでは、親のタグ名を取り出せません。

アノテーション付きテキストを直接イテレータで正規表現検索する利点は計り知れないです。 独自データ構造に対する独自イテレータは、文字より多くの情報を返せる可能性を持ちます。

char

UTF-8 において仮名文字は 3 バイト必要とします。 この 3 バイトを一文字として扱うか実験します。

using namespace wordring::html;

setlocale(LC_ALL, ".UTF8");

// HTML 文書 Tree を作成します。
std::string_view sv = u8"<p>ABCあ</p><p>い</p><p>うDEF</p>";
auto doc = make_document<u8simple_tree>(sv.begin(), sv.end());

// 「文字を走査するイテレータ・アダプタ」を作成します。
auto it = u8simple_tree::character_iterator(doc.begin());

// 正規表現検索します。
std::basic_regex<char> rx("あ.う");
std::match_results<u8simple_tree::character_iterator> mr;
std::regex_search(it, it.end(), mr, rx);

// アサーションは失敗します。
assert(mr.length() == 9);
assert(mr.str() == u8"あいう");

以上のコードで

です。

Windows 、 Linux の両方でマルチバイト文字を一文字と認識しませんでした。 Windows の「ワールドワイド言語サポートで Unicode UTF-8 を使用」はオフのままです。

c++20 で char8_t が標準化されると、以上のコードはコンパイル出来なくなるかもしれません。 char8_tcharではないからです。

その他の選択肢

Google で検索すると、 std::regex 互換のユニコード対応正規表現エンジン実装として SRELL がたびたび紹介されています。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0