使用Boost tokenizer解析含双引号字段的CSV时保留引号的方法
嘿,这个问题我之前处理CSV的时候也踩过坑!Boost Tokenizer里默认的escaped_list_separator是按照标准CSV规则来的——它会自动剥离字段外层的双引号,因为引号在CSV里的作用是包裹包含分隔符、换行或特殊字符的字段,本身不属于字段内容。但如果你的业务场景确实需要保留这些引号,有两个靠谱的办法:
方案1:自定义分隔符处理类(最直接的Tokenizer解决方案)
你可以写一个自定义的separator_function来替代默认的escaped_list_separator,让它主动保留字段的外层引号。下面是一个可直接用的示例:
#include <boost/tokenizer.hpp> #include <string> #include <iostream> #include <cctype> // 自定义保留引号的分隔符处理类 struct QuotePreservingSeparator { typedef boost::tokenizer_detail::iterator_category category; template <typename InputIterator, typename Token> bool operator()(InputIterator& next, InputIterator end, Token& tok) { tok.clear(); // 跳过字段前的空白字符 while (next != end && std::isspace(*next)) { ++next; } if (next == end) return false; // 处理带引号的字段 if (*next == '"') { tok += *next; // 加入开头引号 ++next; // 读取引号内的内容,支持转义引号(比如\") while (next != end && *next != '"') { if (*next == '\\' && next + 1 != end && *(next + 1) == '"') { tok += '"'; // 把转义引号转换成普通引号 next += 2; } else { tok += *next; ++next; } } // 加入结尾引号(如果存在) if (next != end) { tok += *next; ++next; } // 跳过后续的逗号分隔符 while (next != end && *next == ',') ++next; } else { // 处理普通无引号字段 while (next != end && *next != ',') { tok += *next; ++next; } if (next != end) ++next; } return true; } }; int main() { std::string line = "1,\"test\", \"hello\\\"world\""; typedef boost::tokenizer<QuotePreservingSeparator> Tokenizer; Tokenizer tok(line); for (const auto& token : tok) { std::cout << "字段:" << token << std::endl; } // 输出结果: // 字段:1 // 字段:"test" // 字段:"hello\"world" return 0; }
这个类会区分带引号和不带引号的字段:
- 带引号的字段会完整保留开头和结尾的引号,同时正确处理字段内的转义引号(比如
\"会被保留为") - 普通字段依然按逗号正常分隔
方案2:用Boost Spirit解析(更灵活的CSV处理)
如果你的CSV格式比较复杂(比如包含换行、多分隔符等),Boost Tokenizer可能不够灵活,这时候可以试试Boost Spirit。它允许你通过定义语法规则来精确控制解析行为,包括是否保留引号。比如:
#include <boost/spirit/include/qi.hpp> #include <string> #include <vector> #include <iostream> namespace qi = boost::spirit::qi; int main() { std::string line = "1,\"test\", \"hello\\\"world\""; std::vector<std::string> fields; // 定义CSV字段解析规则:要么是带引号的字段(保留引号),要么是普通字段 auto quoted_field = qi::lexeme['"' >> *(qi::char_ - '"' | "\\\"") >> '"']; auto plain_field = qi::lexeme[+(qi::char_ - ',')]; auto csv_line = (quoted_field | plain_field) % ','; bool success = qi::parse(line.begin(), line.end(), csv_line, fields); if (success) { for (const auto& field : fields) { std::cout << "字段:" << field << std::endl; } } else { std::cout << "解析失败" << std::endl; } // 输出和方案1一致 return 0; }
这个方案的优势是语法规则清晰,容易扩展,适合复杂的CSV场景。
如果你的需求只是简单保留引号,方案1完全够用;如果以后要处理更复杂的CSV,方案2会更省心。
内容的提问来源于stack exchange,提问作者dev
相关产品推荐
相关产品推荐

