CSV多行单元格使fgets/std::getline失效,因标准允许双引号内含换行符;需状态机解析,识别未闭合引号并拼接跨行字段,注意BOM、行尾符及引号转义。

CSV 多行单元格为什么会让 fgets 或 std::getline 失效
因为标准 CSV 规范允许用双引号包裹含换行符的字段(例如 Excel、LibreOffice 导出时默认启用),此时一个逻辑单元格会跨多行。若直接按行读取,std::getline 会在第一个换行处截断,导致后续解析错位——字段数对不上、引号不匹配、甚至抛出 std::runtime_error(如 csv::parse_error 类库中常见)。
关键判断点:只有当某行末尾的双引号是**未闭合的**(即引号个数为奇数),才说明该字段尚未结束,需继续读下一行并拼接。
- 不要依赖行计数或固定分隔符切分
- 必须逐字符/逐字段状态机式解析,或使用能识别 quoted newlines 的成熟 parser
- 注意 Windows
\r\n和 Unix\n混用场景,std::getline默认只认\n,但文件可能带\r
手写状态机解析时怎么处理引号转义和嵌套换行
核心是维护三个状态:in_field、in_quoted_field、escaped。遇到 " 时,若前一个字符不是 \(且当前非 escaped 状态),则切换 quoted 状态;若已在 quoted 状态且下一个字符也是 ",则视为转义双引号("" → "),跳过下一个字符并保持状态。
换行符仅在 in_quoted_field 状态下被忽略(即不触发字段结束),否则立即结束当前字段。
- 不要用
std::string::find(",")拆分字段——它无法区分字段内逗号和分隔符 - 读入缓冲区后,建议用
std::vector或std::string_view迭代,避免反复substr开销 - Windows 行尾的
\r必须在进入状态机前清理,否则"abc\r\n"中的\r会被当作普通字符保留
用 rapidcsv 或 csv-parser 库时哪些配置项不能漏
rapidcsv 默认不处理多行字段,必须显式开启:构造 rapidcsv::Document 时传入 rapidcsv::LabelParams{false, false} 并设置 rapidcsv::SeparatorParams{',','\"',true} ——其中第三个参数 true 表示启用 quoted line breaks。
csv-parser(by ben-strasser)更可靠,默认支持,但需确认 csv::Reader 构造时未禁用 csv::REQUIRE_QUOTES,且调用 read_row() 而非 read_line()。
-
rapidcsv的GetCell<:string>(r, c)在多行字段中会自动拼接,但若字段含\r未被 strip,输出字符串里会出现意外回车 - 所有库在遇到非法 quote(如奇数引号未闭合)时行为不同:
rapidcsv抛异常,csv-parser可能静默截断——务必用try/catch包裹首次read_row() - 不要用
std::ifstream::peek()判断是否 EOF 后再读——多行字段可能让流位置和逻辑行不一致
导出 CSV 的程序(如 Excel)悄悄改了什么格式
Excel 默认用 " 包裹所有含逗号、换行、双引号的字段,且把字段内双引号转义为 "";但它**不会**给纯数字字段加引号,哪怕该数字实际是文本(如电话号码 0123)。这意味着同一列可能有时带引号、有时不带,解析器必须兼容两种格式。
更隐蔽的是 BOM:UTF-8 CSV 若含中文,Excel 常加 EF BB BF,而 C++ std::ifstream 默认当普通字符读,导致首字段开头多出乱码。需在读取前检测并跳过。
- 用十六进制查看器确认文件头是否有
EF BB BF,有则is.seekg(3) - 字段内换行在 Excel 中显示为单个
\n,但保存时可能写作\r\n,取决于系统——解析器要同时容忍\n和\r\n作为内部换行 - 空字段在 Excel CSV 中写作
,"",或,,,前者是空字符串,后者是 null/missing,语义不同,业务逻辑需明确区分
多行 CSV 解析真正的复杂点不在算法,而在“谁在什么时候引入了不可见字符”——BOM、混合行尾、引号前后空格、转义规则差异。别假设输入干净,每次打开文件先做 hexdump -C file.csv | head -n 5 看一眼实际字节。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











