如何解析包含多个串联表的 CSV 文件,每个表都有自己的标题行?

How can I parse a CSV file that contains multiple concatenated tables, each with their own header row?

提问人:Arfaoui Arij 提问时间:5/9/2023 最后编辑:user190245Arfaoui Arij 更新时间:5/10/2023 访问量:208

问:

我正在开发一个桌面应用程序,该应用程序将接收多个文件并将其中的信息存储到 SQL 数据库中(基本上就像自动 ETL 过程一样)。

我在解析文件时发现的问题是,一个文件包含多个“表”或标题,而不是我在互联网上找到代码的文件(第一行通常是列名,其余是所有数据)。

有谁知道我如何解析它?

C# .NET CSV 分析

评论

1赞 Dai 5/9/2023
我不知道流行的 NuGet 包是否处理了这个问题 - 但如果没有,我会做 2 次传递:第一遍将使用一个简单的正则表达式来查找这些标头(假设这些标头从未出现在单元格值中......)并存储它们所代表的表的偏移量和长度,然后代理将查找/读取限制在文件中该表的定义范围内的子类(以便您可以继续使用现有的 CSV 库,前提是您可以向它们传递一个对象)。CsvHelperStreamFileStreamStream
3赞 Dai 5/9/2023
此外,StackOverflow 不是 Twitter(谢天谢地......),所以我们在这里不使用主题标签 - 而是使用普通标签,这些标签会自动应用于您的帖子。我冒昧地将它们从您的帖子中删除。
0赞 Dave S 5/9/2023
这不是一个普通的 CSV,但如果 @Dai5 的答案不适合您,您可以将文件预处理为一组临时文件(在边界处拆分),而不是使用子类流。
1赞 Dai 5/9/2023
@DaveS 你错过了我计划中的一个致命缺陷:你不能使用 - 只能 - 但将数据文件加载到内存中的想法是噩梦般的燃料RegexStreamStringString
0赞 Dave S 5/9/2023
如果标题行相同,则预处理可以使用基本字符串比较。传递 1 - 逐行读取以查找中断,并为流子类构建偏移表,或拆分为多个临时文件。第 2 步 - 重新开始处理为子类流或一组临时文件 当然,如果只有 1-2 个奇怪的、非正常的 CSV,选项 3 是“使用文本编辑器来修复它们”

答:

0赞 MarkPflug 5/9/2023 #1

我维护一个可能适合您的库:Sylvan.Data.Csv。它支持包含“多个表”的 CSV 文件。要使此功能正常工作,它要求每个表具有不同数量的列,或者它们之间有一个空行。

以下示例演示如何使用“MutliResult”模式:

using Sylvan.Data.Csv;

var data =
    """
    a,b,c
    1,2,3
    d,e,f,g
    4,5,6,7
    """;

// MutliResult will identify a new "table" any time the number of columns changes
// any empty lines between tables are skipped.
var opts = new CsvDataReaderOptions { ResultSetMode = ResultSetMode.MultiResult };

// CsvDataReader can be used as a DbDataReader, so can be fed directly to SqlBulkCopy.
System.Data.Common.DbDataReader csv = CsvDataReader.Create(new StringReader(data), opts);

do
{
    Console.WriteLine(csv.GetName(0));
    while (csv.Read())
    {
        Console.WriteLine(csv.GetString(0));
    }
    Console.WriteLine("---");
} while (csv.NextResult());
// outputs:
// a
// 1
// ---
// d
// 4
// ---

但是,如果两个连续的表包含相同数量的列,并且没有空行分隔它们,则此功能将不起作用,因为它会将后续表视为第一个表的延续。

在这种情况下,可以使用 Sylvan.Data 库的一项功能来确定下一个表的开始位置。

此示例使用“TakeWhile”扩展方法来标识下一个表的开始时间。

using Sylvan.Data;
using Sylvan.Data.Csv;

var data =
    """
    a,b,c
    1,2,3
    d,e,f
    4,5,6
    """;

var csv = CsvDataReader.Create(new StringReader(data));

// the batchReader will read until it finds a row starting with "d".
// you can customize this logic to identify when the next table starts in your data.
// The batchReader here is a wrapper around the csv reader, and will yield rows as long as the "TakeWhile"
// predicate is true
System.Data.Common.DbDataReader batchReader = csv.TakeWhile(r => r.GetString(0) != "d");

// process the table using the standard DbDataReader APIs.
while (batchReader.Read())
{
    Console.WriteLine(csv.GetName(0));
    Console.WriteLine(batchReader.GetString(0));
}

Console.WriteLine("---");

// The csv reader is now positioned on the start of the next table
// calling initialize will re-initialize the CsvDataReader with the current row
// this will cause the "d,e,f" headers to be loaded.
csv.Initialize();

// consume the rest of the CSV data.
// Or, you might need to use another TakeWhile.
while (csv.Read())
{
    Console.WriteLine(csv.GetName(0));
    Console.WriteLine(csv.GetString(0));
}

Console.WriteLine("---");

第二个示例生成的输出与第一个示例相同。