std::regex
を独自イテレータに適用する場合の可用性について考察しました。
要約
-
wchar_t
を使えば、 Windows と Linux でOKでした -
char
はマルチバイト文字を一文字と認識しませんでした
考察
std::regex
は、ユニコードへの対応を求められていません。 http://www.akenotsuki.com/misc/srell/uniregex.html によると、 C++ 標準化委員会は、 std::regex のユニコード対応に後ろ向きなようです。
std::regex
は、char8_t
、char16_t
、char32_t
をサポートしません。std::regex
は、char
、wchar_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_t
は char16_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"あいう");
以上のコードで
- make_document() は、 HTML 文書を返す HTML パーサーの便利関数
- u8simple_tree は、 HTML 文書を格納する Tree
- character_iterator は、「文字を走査するイテレータ・アダプタ」
です。
Windows 、 Linux の両方でマルチバイト文字を一文字と認識しませんでした。 Windows の「ワールドワイド言語サポートで Unicode UTF-8 を使用」はオフのままです。
c++20 で char8_t が標準化されると、以上のコードはコンパイル出来なくなるかもしれません。 char8_t
はchar
ではないからです。
その他の選択肢
Google で検索すると、 std::regex 互換のユニコード対応正規表現エンジン実装として SRELL がたびたび紹介されています。