【问题标题】:Dealing with fields containing unescaped double quotes with TextFieldParser使用 TextFieldParser 处理包含未转义双引号的字段
【发布时间】:2013-04-20 00:09:04
【问题描述】:

我正在尝试使用 TextFieldParser 导入 CSV 文件。一个特定的 CSV 文件由于其非标准格式而给我带来了问题。有问题的 CSV 的字段用双引号括起来。当特定字段中有一组额外的未转义双引号时,就会出现问题。

这是一个强调问题的过度简化的测试用例。我正在处理的实际 CSV 文件的格式并不完全相同,并且有几十个字段,其中任何一个都可能包含这些可能很棘手的格式问题。

TextReader reader = new StringReader("\"Row\",\"Test String\"\n" +
    "\"1\",\"This is a test string.  It is parsed correctly.\"\n" +
    "\"2\",\"This is a test string with a comma,  which is parsed correctly\"\n" +
    "\"3\",\"This is a test string with double \"\"double quotes\"\". It is parsed correctly\"\n" +
    "\"4\",\"This is a test string with 'single quotes'. It is parsed correctly\"\n" +
    "5,This is a test string with fields that aren't enclosed in double quotes.  It is parsed correctly.\n" +
    "\"6\",\"This is a test string with single \"double quotes\".  It can't be parsed.\"");

using (TextFieldParser parser = new TextFieldParser(reader))
{
    parser.Delimiters = new[] { "," };
    while (!parser.EndOfData)
    {
        string[] fields= parser.ReadFields();
        Console.WriteLine("This line was parsed as:\n{0},{1}",
            fields[0], fields[1]);
    }
}

是否可以使用 TextFieldParser 正确解析具有这种格式的 CSV?

【问题讨论】:

  • 不要试图修复它是非常重要的。这将使您长期对不良数据负责。拒绝文件格式不正确。如果他们为此困扰您,请指出它与 RFC-4180 不兼容。有另一个程序员可以轻松解决这个问题。
  • @HansPassant 虽然这是理想且“正确”的行动方案,但很多时候我们别无选择,例如在使用我们无法控制的 API 中的文件时,或者客户很重要,我们只需要“让它发挥作用”。

标签: c# parsing csv file-io


【解决方案1】:

Jordan 的解决方案非常好,但它错误地假设错误行总是以双引号开头。我的错误行是这样的:

170,"CMS ALT",853,,,NON_MOVEX,COM,NULL,"2014-04-25",""  204 Route de Trays"

注意最后一个字段有多余的/未转义的双引号,但第一个字段很好。所以乔丹的解决方案没有奏效。这是我根据 Jordan 的修改后的解决方案:

using(TextFieldParser parser = new TextFieldParser(new StringReader(csv))) {
 parser.Delimiters = new [] {","};

 while (!parser.EndOfData) {
  string[] fields = null;
  try {
   fields = parser.ReadFields();
  } catch (MalformedLineException ex) {
   string errorLine = SafeTrim(parser.ErrorLine);
   fields = errorLine.Split(',');
  }
 }
}

您可能希望以不同的方式处理 catch 块,但一般概念对我来说非常有用。

【讨论】:

    【解决方案2】:

    如果您不设置 HasFieldsEnclosedInQuotes = true,如果数据包含 (,) 逗号,则生成的列列表将更多。 例如 "Col1","Col2","Col3" “测试1”,100,“测试1,测试2” “测试2”,200,“测试22” 这个文件应该有 3 列,但在解析时你会得到 4 个错误的字段。

    【讨论】:

      【解决方案3】:

      我同意 Hans Passant 的建议,即解析格式错误的数据不是您的责任。然而,根据Robustness Principle,面临这种情况的人可能会尝试处理特定类型的畸形数据。我在下面编写的代码适用于问题中指定的数据集。基本上它会在格式错误的行上检测解析器错误,根据第一个字符确定它是否被双引号包裹,然后手动拆分/剥离所有包裹双引号。

      using (TextFieldParser parser = new TextFieldParser(reader))
      {
          parser.Delimiters = new[] { "," };
      
          while (!parser.EndOfData)
          {
              string[] fields = null;
              try
              {
                  fields = parser.ReadFields();
              }
              catch (MalformedLineException ex)
              {
                  if (parser.ErrorLine.StartsWith("\""))
                  {
                      var line = parser.ErrorLine.Substring(1, parser.ErrorLine.Length - 2);
                      fields = line.Split(new string[] { "\",\"" }, StringSplitOptions.None);
                  }
                  else
                  {
                      throw;
                  }
              }
              Console.WriteLine("This line was parsed as:\n{0},{1}", fields[0], fields[1]);
          }
      }
      

      我确信可以编造一个失败的病态示例(例如,在字段值中与双引号相邻的逗号),但任何此类示例在最严格的意义上可能是不可解析的,而问题行在尽管格式错误,但该问题仍可解读。

      【讨论】:

      • 考虑到我发布这个问题已经快两年了,我不确定这是否能解决我最初的问题。我最终接受了 Hans 的建议,并要求提供更接近规范的文件。由于这确实解决了我的示例案例并且我从未接受过答案,因此我将继续接受您的答案。谢谢,你让我免于成为另一个 DenverCoder9 - xkcd.com/979
      • 啊,必须的 XKCD :) 是的,我知道我打开了一个老问题,但我有一个与您的问题非常相似的问题,当我找到解决方案时,我认为最好分享吧。
      【解决方案4】:

      工作解决方案:

      using (TextFieldParser csvReader = new TextFieldParser(csv_file_path))
                  {
                      csvReader.SetDelimiters(new string[] { "," });
                      csvReader.HasFieldsEnclosedInQuotes = false;
                      string[] colFields = csvReader.ReadFields();
      
                      while (!csvReader.EndOfData)
                      {
                          string[] fieldData = csvReader.ReadFields();
                          for (i = 0; i < fieldData.Length; i++)
                          {
                              if (fieldData[i] == "")
                              {
                                  fieldData[i] = null;
                              }
                              else
                              {
                                  if (fieldData[i][0] == '"' && fieldData[i][fieldData[i].Length - 1] == '"')
                                  {
                                      fieldData[i] = fieldData[i].Substring(1, fieldData[i].Length - 2);
                                  }
                              }
                          }
                          csvData.Rows.Add(fieldData);
                         }
                  }
      

      【讨论】:

      • 澄清一下,测试字符串 #2 中的逗号会导致字段拆分错误。
      【解决方案5】:

      在开始读取文件之前,请在 TextFieldParser 对象上设置 HasFieldsEnclosedInQuotes = true

      【讨论】:

      • 该选项可以打开,TextParser 仍然无法解析文本。问题不在于这些字段用引号引起来,而是在字段中有些引号没有通过使用两个引号正确转义。
      【解决方案6】:

      手动执行此操作可能更容易,而且肯定会给您更多控制:

      编辑: 对于您澄清的示例,我仍然建议手动处理解析:

      using System.IO;
      
      string[] csvFile = File.ReadAllLines(pathToCsv);
      foreach (string line in csvFile)
      {
          // get the first comma in the line
          // everything before this index is the row number
          // everything after is the row value
          int firstCommaIndex = line.IndexOf(',');
      
          //Note: SubString used here is (startIndex, length) 
          string row = line.Substring(0, firstCommaIndex+1);
          string rowValue = line.Substring(firstCommaIndex+1).Trim();
      
          Console.WriteLine("This line was parsed as:\n{0},{1}",
                  row, rowValue);
      }
      

      对于不允许在字段中使用逗号的通用 CSV:

      using System.IO;
      
      string[] csvFile = File.ReadAllLines(pathToCsv);
      foreach (string line in csvFile)
      {
          string[] fields = line.Split(',');
          Console.WriteLine("This line was parsed as:\n{0},{1}",
                  fields[0], fields[1]);
      }
      

      【讨论】:

      • 看起来我可能已经简化了我的示例,以至于我不清楚为什么要使用 TextFieldParser。逗号上的简单拆分最终会引入使用 TextFieldParser 时不存在的各种不同问题。主要示例是特定文本值中存在逗号。我将使用更复杂的测试字符串更新问题,以突出使用 TextFieldParser 的好处。
      • @sglantz :更新的代码示例。我仍然建议您手动进行最大控制。我发现 CSV 和其他解析器的问题在于,即使它们被编程为非常通用,也很容易拥有无法与它们一起使用的数据。我认为新的代码示例更易于阅读,但也可以使用 C# 正则表达式和 Match 类来完成。
      • 看起来该示例仍然没有传达我正在处理的 CSV 文件的复杂性。它们的格式不一致。逗号和引号可能出现在文件的 20 多个字段中的任何一个中。 TextFieldParser 擅长处理这种不一致性,同时手动拆分甚至正则表达式在处理各种不同格式时会很快变得非常复杂。
      猜你喜欢
      • 1970-01-01
      • 2021-12-27
      • 2023-01-07
      • 1970-01-01
      • 1970-01-01
      • 2022-10-14
      • 2011-01-01
      • 1970-01-01
      相关资源
      最近更新 更多