了解 CSV 格式:为什么常用工具偏离 RFC 4180 标准?

问题描述 投票:0回答:1

我正在尝试解析大量 CSV 文件(逗号分隔,UTF-8 编码),但遇到了反复出现的问题。

考虑一个场景,其中我有三个字段,其值是::

A, "B", C
。根据 RFC 4180

如果使用双引号来括住字段,则必须通过在字段前添加另一个双引号来转义出现在字段内的双引号

基于此,我的理解是正确的CSV表示应该是

A,"""B""",C
。但是,许多由各种工具生成的文件的格式为::
"A, ""B"", C"

这会导致 CSV 解析器(例如 C# 中的 CsvHelper)将此类行解释为单个字段,而不是三个单独的字段。

我在这里遗漏了什么吗?为什么这种看似“不正确”的格式在不同的工具中如此普遍使用?

为了讨论,这里有一个更现实的例子:

"00AA12345,30/11/2023,30/11/2023,01/12/2023,01/12/2023,""BAS"",1 111 000.27,""NRT"",""Test, ok"","""","""","""","""","""""

我需要读书

  • 字段 0:id
  • 字段 1 至 4:日期
  • 字段 5:日期
  • 字段 6:字符串
  • 字段 7 和 + :字符串(可以包含双引号)
c# csv csvhelper
1个回答
0
投票

CsvMode.Escape
接近您正在寻找的东西。它适用于您非常简单的
"A, ""B"", C"
示例。然而,对于
""Test, ok""
,它创建了两个字段
"Test
ok"
,我怀疑这应该是一个字段。但也许我错了,这对你有用,所以我想我至少会建议它。

var config = new CsvConfiguration(CultureInfo.InvariantCulture) {
    Mode = CsvMode.Escape   
};
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader, config))

我倾向于同意@PanagiotisKanavos 的观点,即该文件已被编码/解析两次。这就是为什么我建议阅读两遍。首先读取它,就像它应该是单个字段一样,然后读取该字段以获取记录。

这会删除您所说的那些双引号,但我不相信它们应该在那里。

void Main()
{
    var sb = new StringBuilder();
    sb.Append("\"00AA12345,30/11/2023,30/11/2023,01/12/2023,01/12/2023");
    sb.Append(",\"\"BAS\"\",1 111 000.27,\"\"NRT\"\",\"\"Test, ok\"\"");
    sb.Append(",\"\"\"\",\"\"\"\",\"\"\"\",\"\"\"\",\"\"\"\"\"");
    sb.AppendLine();
    sb.Append("\"00AA12345,30/11/2023,30/11/2023,01/12/2023,01/12/2023");
    sb.Append(",\"\"BAS\"\",1 111 000.27,\"\"NRT\"\",\"\"Test, ok\"\"");
    sb.Append(",\"\"\"\",\"\"\"\",\"\"\"\",\"\"\"\",\"\"\"\"\"");

    var records = new List<Foo>();

    var config = new CsvConfiguration(CultureInfo.InvariantCulture) {
        HasHeaderRecord = false
    };
    
    using (var reader = new StringReader(sb.ToString()))
    using (var csv = new CsvReader(reader, config))
    {
        while(csv.Read())
        {
            var line = csv.GetRecord<SingleLine>().Line;

            using (var reader2 = new StringReader(line))
            using (var csv2 = new CsvReader(reader2, config))
            {
                if(csv2.Read())
                {
                    var options = new TypeConverterOptions { Formats = new[] { "dd/MM/yyyy" } };
                    csv2.Context.TypeConverterOptionsCache.AddOptions<DateTime>(options);

                    var record = csv2.GetRecord<Foo>();
                    records.Add(record);
                }               
            }
        }       
    }
    records.Dump();
}

public class SingleLine
{
    public string Line { get; set; }
}

public class Foo
{
    [Index(0)]
    public string Field0 { get; set; }
    [Index(1)]
    public DateTime Field1 { get; set; }
    [Index(2)]
    public DateTime Field2 { get; set; }
    [Index(3)]
    public DateTime Field3 { get; set; }
    [Index(4)]
    public DateTime Field4 { get; set; }
    [Index(5)]
    public string Field5 { get; set; }
    [Index(6)]
    public string Field6 { get; set; }
    [Index(7)]
    public string Field7 { get; set; }
    [Index(8)]
    public string Field8 { get; set; }
    [Index(9)]
    public string Field9 { get; set; }
    [Index(10)]
    public string Field10 { get; set; }
    [Index(11)]
    public string Field11 { get; set; }
    [Index(12)]
    public string Field12 { get; set; }
    [Index(13)]
    public string Field13 { get; set; }
}
© www.soinside.com 2019 - 2024. All rights reserved.