"123Akira25"
のような固定長データを解析する処理を考えます。
まず、簡単なところではbasic_string::substr()を使う方法があります。
#include <string> int main() { const std::string s = "123Akira25"; const std::string id = s.substr(0, 3); const std::string name = s.substr(3, 5); const std::string age = s.substr(8); // s.substr(8, 2)でもいいけど「残り全部」という意図 }
これはうっかりミスが多いので、オフセットを最初に定義しましょう。
#include <string> int main() { const std::string s = "123Akira25"; const std::size_t offsets[] = { 3, 5, 2 }; std::size_t ofs = 0; const std::string id = s.substr(ofs, offsets[0]); ofs += offsets[0]; const std::string name = s.substr(ofs, offsets[1]); ofs += offsets[1]; const std::string age = s.substr(ofs); // これもs.substr(ofs, offsets[2])でもいい }
いくらかマシになりましたが、管理がめんどう。
もうちょいいじれるけど、やっぱりめんどうなので省略。
次に、Boost.Tokenizerを使う方法。
#include <string> #include <boost/tokenizer.hpp> #include <boost/range.hpp> int main() { const std::string s = "123Akira25"; const int offsets[] = { 3, 5, 2 }; const boost::offset_separator sep(boost::begin(offsets), boost::end(offsets)); typedef boost::tokenizer<boost::offset_separator> tokenizer; const tokenizer tok(s, sep); tokenizer::const_iterator it = tok.begin(); const std::string id = *it++; const std::string name = *it++; const std::string age = *it; }
オフセットの管理をまかせられるので、良くなったようにも見えますが、
Tokenizerだと「残り全部」という処理ができないため、
数量:N データ0 ・ ・ ・ データN-1
のような、個数によって長さが変わるようなデータがあった場合に対応できません。
#include <iostream> #include <string> #include <boost/tokenizer.hpp> #include <boost/range.hpp> int main() { const std::string s = "abcde3xxxyyyzzz"; // いらないデータ, 個数, N個のデータ const int offsets[] = { 5, 1 }; const boost::offset_separator sep(boost::begin(offsets), boost::end(offsets)); typedef boost::tokenizer<boost::offset_separator> tokenizer; const tokenizer tok(s, sep); tokenizer::const_iterator it = tok.begin(); const std::string hoge = *it++; const std::string count = *it++; const std::string data = *it; // "xxxyyyzzz"を取得したい std::cout << hoge << "," << count << "," << data << std::endl; }
abcde,3,xxxyy
「残り全部」という意図に反し、オフセットがループしてしまってますね。
最後に全てを解決するBoost.Spirit.Qi。
#include <string> #include <boost/spirit/include/qi.hpp> namespace qi = boost::spirit::qi; namespace fusion = boost::fusion; int main() { const std::string s = "123Akira25"; typedef fusion::vector<std::string, std::string, std::string> result_type; typedef qi::rule<std::string::const_iterator, result_type()> rule; const rule r = qi::repeat(3)[ qi::char_ ] >> qi::repeat(5)[ qi::char_ ] >> *qi::char_; // 残り全部 result_type result; std::string::const_iterator it = s.begin(); qi::parse(it, s.end(), r, result); const std::string& id = fusion::at_c<0>(result); const std::string& name = fusion::at_c<1>(result); const std::string& age = fusion::at_c<2>(result); }
「残り全部」の処理も、オフセットの管理も簡単です。
さらに、BOOST_FUSION_ADAPT_STRUCTを使えば構造体に一発変換もできます。
#include <iostream> #include <string> #include <boost/spirit/include/qi.hpp> #include <boost/fusion/include/struct.hpp> namespace qi = boost::spirit::qi; struct person { std::string id; std::string name; std::string age; }; BOOST_FUSION_ADAPT_STRUCT( person, (std::string, id) (std::string, name) (std::string, age) ) int main() { const std::string s = "123Akira25"; typedef person result_type; typedef qi::rule<std::string::const_iterator, result_type()> rule; const rule r = qi::repeat(3)[ qi::char_ ] >> qi::repeat(5)[ qi::char_ ] >> *qi::char_; result_type result; std::string::const_iterator it = s.begin(); qi::parse(it, s.end(), r, result); std::cout << result.id << "," << result.name << "," << result.age << std::endl; }
123,Akira,25
Spirit.Qiを使う場合の注意点としては、デフォルトで扱えるのが10フィールドまで、ということです。
これは、Boost.Fusionのシーケンスの最大要素数がデフォルト10で定義されているためです。
なので、それよりも多くのフィールドを扱う場合は、
#define FUSION_MAX_VECTOR_SIZE 15
のようにサイズを変更する必要があります。
それと、さすがに戻り値の型にこんなのを書きたくはないので
typedef
fusion::vector<
std::string,
std::string,
std::string,
std::string,
std::string,
std::string,
std::string,
std::string,
std::string,
std::string,
std::string,
std::string,
std::string,
std::string,
std::string
>
result_type;
同じ型の場合には、N個のTから成るFusionのシーケンスを作りましょう。
それらしいメタ関数が用意されていなかったので、boost::arrayをFusionのシーケンスにアダプトします。
#include <boost/fusion/include/boost_array.hpp> ... typedef fusion::result_of::as_vector<boost::array<std::string, 15> >::type result_type;
これでコンパイラが許す限り、長さを制御できます。