ASP.NET MVC中使用StreamReader解析CSV如何处理单元格内换行?
处理CSV中包含换行符的单元格问题
你遇到的这个问题其实很常见——CSV规范里允许单元格内容包含换行符,只要这个单元格是用双引号包裹起来的,但ReadLine()方法只会把换行符当作行的结束标志,完全不管它是不是在引号内部,所以才会把同一个单元格拆成两行。
你现在用的Regex.Split虽然尝试处理引号内的逗号,但因为已经被ReadLine()拆成了多行,所以根本没办法恢复原来的完整单元格。下面给你两种解决思路:
推荐方案:使用成熟的CSV解析库(比如CsvHelper)
自己手动处理CSV的各种边缘情况(比如引号内的换行、转义引号)很容易踩坑,不如直接用业界常用的库来搞定。CsvHelper是一个非常流行的.NET CSV处理库,对这种包含换行的单元格支持得很好。
步骤:
- 先通过NuGet安装CsvHelper包;
- 修改你的解析代码,示例如下:
using CsvHelper; using System.Globalization; public static List<T> ConvertCsvToDataTable<T>(StreamReader csvReader, ref Dictionary<int, List<string>> failures) where T : new() { var objects = new List<T>(); failures = new Dictionary<int, List<string>>(); using var csv = new CsvReader(csvReader, CultureInfo.InvariantCulture); // 配置自动映射到你的实体类T(确保实体类属性名和CSV列名匹配,或者用特性指定) var records = csv.GetRecords<T>(); int lineNumber = 2; // 表头是第一行,数据从第二行开始 foreach (var record in records) { // 这里可以保留你的赋值校验逻辑,把失败记录存入failures var lineFailures = AssignValuesFromCsv(record, /* 若需要可从csv.Context获取行数据 */, columnIndex); if (lineFailures.Count > 0) { failures.Add(lineNumber, lineFailures); } objects.Add(record); lineNumber++; } return objects; }
CsvHelper会自动处理引号内的换行、转义字符等所有CSV规范里的特殊情况,比自己写正则靠谱多了。
自定义处理方案(不依赖第三方库)
如果你不想引入第三方库,可以修改你的代码逻辑,不再用ReadLine()逐行读取,而是先判断当前行的引号是否成对,不成对的话就继续读取下一行拼接起来,直到引号成对为止。
修改后的核心逻辑示例:
public static List<T> ConvertCsvToDataTable<T>(StreamReader csvReader, ref Dictionary<int, List<string>> failures) where T : new() { var objects = new List<T>(); var columnIndex = new Dictionary<string, int>(); var headerLine = csvReader.ReadLine(); var headers = headerLine.Split(Convert.ToChar(",")); for (var i = 0; i < headers.Length; i++) { columnIndex.Add(headers[i], i); } var lineNumber = 1; while (!csvReader.EndOfStream) { // 初始读取一行 var line = csvReader.ReadLine(); if (string.IsNullOrEmpty(line)) continue; // 检查当前行的双引号数量是否为奇数,如果是,说明单元格未结束,继续读下一行拼接 while (line.Count(c => c == '"') % 2 != 0 && !csvReader.EndOfStream) { line += Environment.NewLine + csvReader.ReadLine(); } var obj = new T(); // 用原来的正则拆分逻辑处理完整的行 var csvRow = Regex.Split(line, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"); var lineFailures = AssignValuesFromCsv(obj, csvRow, columnIndex); if (lineFailures.Count > 0) failures.Add(lineNumber, lineFailures); objects.Add(obj); lineNumber++; } return objects; }
这里的关键是判断当前行的双引号数量是否为偶数——如果是奇数,说明这一行的引号没有闭合,意味着换行是单元格内的内容,需要把下一行拼接到当前行,直到引号成对为止。
不过要注意,这个自定义逻辑只处理了引号内的换行,还有一些其他CSV特殊情况(比如单元格内的转义双引号"")没处理,所以如果你的CSV有这些情况,还是推荐用CsvHelper这类成熟库。
内容的提问来源于stack exchange,提问作者Rita
相关产品推荐
相关产品推荐

