在 .NET 中解析分隔的 CSV

Posted

技术标签:

【中文标题】在 .NET 中解析分隔的 CSV【英文标题】:Parse Delimited CSV in .NET 【发布时间】:2010-10-18 17:00:16 【问题描述】:

我有一个以逗号分隔格式的文本文件,在大多数字段中由" 分隔。我正在尝试将其转化为可以枚举的内容(例如,通用集合)。我无法控制文件的输出方式,也无法控制用于分隔符的字符。

在这种情况下,字段用逗号分隔,文本字段用" 标记括起来。我遇到的问题是某些字段中包含引号(即 8" Tray)并且不小心被选为下一个字段。对于数字字段,它们周围没有引号,但它们确实以 + 或 - 符号开头(表示正/负数)。

我正在考虑 RegEx,但我的技能不是那么好,所以希望有人能提出一些我可以尝试的想法。这个文件中有大约 19,000 条记录,所以我试图尽可能高效地完成它。以下是几行示例数据:

"00","000000112260   ","Pie Pumpkin                             ","RET","6.99 ","     ","ea ",+0000000006.99000
"00","000000304078   ","Pie Apple caramel                       ","RET","9.99 ","     ","ea ",+0000000009.99000
"00","StringValue here","8" Tray of Food                             ","RET","6.99 ","     ","ea ",-00000000005.3200

还有很多字段,但你可以得到图片....

我正在使用 VB.NET,并且我有一个通用的列表设置来接受数据。我尝试过使用CSVReader,它似乎运行良好,直到您达到第三条记录(在文本字段中带有引号)。如果我能以某种方式让它处理额外的引号,那么 CSVReader 选项会很好用。

谢谢!

【问题讨论】:

从正确格式化 CSV 文件开始会有所帮助。 带引号的字符串中的双引号应该通过加倍进行转义。所以"8" Tray of Food" 是不允许出现在格式中的。这样的话,像hi","there 这样的字符串就可以存在了。转义和引用,变成"hi"",""there"。没有加倍,它变成 "hi","there" 看起来像两个字符串。 我同意上述两个 cmets,但不幸的是,我无法控制文件的导出方式。这就是它从软件中出来的方式。 请,请,请不要滚动您自己的 CSV 解析器,当然也不要为此使用 RegEx。使用免费、开源、久经考验的 FileHelpers 库。 filehelpers.com filehelpers 源不再可用 :( 【参考方案1】:

CSV 文件至少有 ODBC 驱动程序。但是 CSV 有不同的风格。

是什么产生了这些文件?根据源应用程序的要求,不太可能存在匹配的驱动程序。

【讨论】:

这是一个旧的基于 DOS 的会计软件包,称为 Business Vision Delta。不幸的是,该公司已被出售给新的供应商,他们不再支持旧的 DOS 东西。这是我可以提取数据以集成到更新软件中的唯一方法。 你能告诉它使用什么样的数据表吗?也许是dbfs?此外,尝试使用 Excel、Access 以及任何其他可以导入 CSV 的应用程序打开 CSV 文件。尽量避免将编写软件作为第一选择。【参考方案2】:

看看FileHelpers library。

【讨论】:

看起来不错,但我发现它使用起来非常令人沮丧。缺乏对自动属性而不是私有字段的支持非常笨拙。 这不是原始问题中的一个因素,但该页面说 FileHelpers 使用动态代码生成。这意味着它在某些受限环境中没有用(对我来说是 MonoTouch)。【参考方案3】:

您对 CSVReader 的问题是第三条记录中的引号没有被另一个引号(也称为双引号)转义。如果您不转义它们,那么您希望如何处理文本字段中间的 "?

http://en.wikipedia.org/wiki/Comma-separated_values

(我最终不得不处理文件(使用不同的分隔符),但文本值中的引号字符没有被转义,我最终编写了自己的自定义解析器。我不知道这是否是绝对必要的,或者不是。)

【讨论】:

这是我的问题...我无法逃脱他们。我无法控制文件的导出方式。我试图避免编写一个逐字符检查引号后是否有逗号等的解析器,但它可能归结为这一点。 好吧,如果你走自己的路(我仍然相信有一个解决方案可以处理这种情况),请确保尽可能地验证字段计数和数据能够。 (我会发布我的,但我是在工作中发布的。)【参考方案4】:

来自here:

Encoding fileEncoding = GetFileEncoding(csvFile);
// get rid of all doublequotes except those used as field delimiters
string fileContents = File.ReadAllText(csvFile, fileEncoding);
string fixedContents = Regex.Replace(fileContents, @"([^\^,\r\n])""([^$,\r\n])", @"$1$2");
using (CsvReader csv =
       new CsvReader(new StringReader(fixedContents), true))

       // ... parse the CSV

【讨论】:

这很好用,但由于某种原因,它搞砸了一个像这样的名称:产品“A”名称我确信它与正则表达式有关,但我似乎无法得到没错。 请参阅下面的答案,了解我是如何实现这一点的。 这是我使用的一个很好的解决方案,但 GetFileEncoding 函数不可用。如果有人需要,我稍后会发布。【参考方案5】:

我建议查看 .Net 中的 TextFieldParserClass。你需要包括

Imports Microsoft.VisualBasic.FileIO.TextFieldParser

这是一个简单的示例:

        Dim afile As FileIO.TextFieldParser = New FileIO.TextFieldParser(FileName)
        Dim CurrentRecord As String() ' this array will hold each line of data
        afile.TextFieldType = FileIO.FieldType.Delimited
        afile.Delimiters = New String() ","
        afile.HasFieldsEnclosedInQuotes = True

        ' parse the actual file
        Do While Not afile.EndOfData
            Try
                CurrentRecord = afile.ReadFields
            Catch ex As FileIO.MalformedLineException
                Stop
            End Try
        Loop

【讨论】:

仅供参考:TextFieldParser 实现 IDisposable 并且应该包含在“使用”语句中或显式处置。 如果引用字段中有换行符,这似乎不起作用。无赖。 有谁知道为什么这样一个通用类会存在于 VisualBasic 命名空间中? 2021 签入——Avi 的答案仍然是我使用 Framework 4.6 解析某些字段嵌入逗号的 CSV 的最佳解决方案。来这里是因为其他方法不太好用。【参考方案6】:

这种自定义方法的逻辑是:一次读取文件1行,用逗号分隔每一行,删除第一个和最后一个字符(删除外引号但不影响任何内引号),然后添加数据到您的通用列表。它很短,很容易阅读和使用。

        Dim fr As StreamReader = Nothing
        Dim FileString As String = ""
        Dim LineItemsArr() as String

        Dim FilePath As String = HttpContext.Current.Request.MapPath("YourFile.csv")

        fr = New System.IO.StreamReader(FilePath)

        While fr.Peek <> -1
            FileString = fr.ReadLine.Trim

            If String.IsNullOrEmpty(FileString) Then Continue While 'Empty Line

            LineItemsArr = FileString.Split(",")

            For Each Item as String In LineItemsArr
                'If every item will have a beginning and closing " (quote) then you can just
                'cut the first and last characters of the string here.
                'i.e.  UpdatedItems = Item. remove first and last character

                'Then stick the data into your Generic List (Of String()?)
            Next
        End While

【讨论】:

或者在去掉外引号之前,用它作为测试来做字符串处理,或者数字处理(如果需要的话)。 这不处理数据中分隔符所在的数据【参考方案7】:

我将此作为答案发布,以便我可以解释我是如何做到的以及为什么.... Mitch Wheat 的答案是为我提供此案例的最佳解决方案的答案,我只需要稍微修改一下即可导出此数据的格式。

这是 VB 代码:

Dim fixedContents As String = Regex.Replace(
                            File.ReadAllText(csvFile, fileEncoding),
                            "(?<!,)("")(?!,)", 
                            AddressOf ReplaceQuotes)

我需要更改使用的 RegEx,因为某些字段中包含非转义引号,并且提供的 RegEx 似乎不适用于所有示例。这个使用“向前看”和“向后看”来查看引号是在逗号之后还是之前。在这种情况下,它们都是否定的(意味着告诉我双引号不在逗号之前或之后的位置)。这应该意味着引号在字符串的中间。

在这种情况下,我没有直接替换,而是使用函数 ReplaceQuotes 来为我处理。我使用它的原因是因为我需要一些额外的逻辑来检测它是否在行首。如果我会花更多时间在上面,我相信我可以调整 RegEx 以考虑行的开头(使用 MultiLine 等),但是当我快速尝试时,它似乎并没有在全部。

有了这个,在 32MB CSV 文件(大约 19000 行)上使用 CSV 阅读器,读取文件、执行正则表达式、将其加载到 CSV 阅读器、将所有数据添加到我的通用文件大约需要 2 秒上课并完成。真快!!

【讨论】:

【参考方案8】:

试试这个网站。 http://kbcsv.codeplex.com/

我一直在寻找一个好的实用程序,这是我找到的最好的并且可以正常工作的实用程序。不要浪费时间尝试其他东西,这是免费的,而且很有效。

【讨论】:

【参考方案9】:

正如这个链接所说...Don't roll your own CSV parser!

按照 Avi 的建议使用 TextFieldParser。 Microsoft 已经为您完成了这项工作。如果您最终编写了一个,并且发现其中存在错误,请考虑替换它而不是修复错误。我最近就这样做了,它为我节省了很多时间。

【讨论】:

【参考方案10】:
        public static Encoding GetFileEncoding(String fileName)
    
        Encoding Result = null;
        FileInfo FI = new FileInfo(fileName);
        FileStream FS = null;

        try
        
            FS = FI.OpenRead();
            Encoding[] UnicodeEncodings =  Encoding.BigEndianUnicode, Encoding.Unicode, Encoding.UTF8 ;
            for (int i = 0; Result == null && i < UnicodeEncodings.Length; i++)
            
                FS.Position = 0;
                byte[] Preamble = UnicodeEncodings[i].GetPreamble();
                bool PreamblesAreEqual = true;
                for (int j = 0; PreamblesAreEqual && j < Preamble.Length; j++)
                
                    PreamblesAreEqual = Preamble[j] == FS.ReadByte();
                
                if (PreamblesAreEqual)
                
                    Result = UnicodeEncodings[i];
                
            
        
        catch (System.IO.IOException)
        
        
        finally
        
            if (FS != null)
            
                FS.Close();
            
        

        if (Result == null)
        
            Result = Encoding.Default;
        

        return Result;
    

【讨论】:

【参考方案11】:

排除第一个和最后一个引号的正则表达式是(?&lt;!^)(?&lt;!,)("")(?!,)(?!$)。当然,你需要使用RegexOptions.Multiline。

这样就不需要评估器功能。我的代码用单引号替换了不需要的双引号。

完整的C#代码如下。

string fixedCSV = Regex.Replace(
            File.ReadAllText(fileName),
            @"(?<!^)(?<!;)("")(?!;)(?!$)", "'", RegexOptions.Multiline);

【讨论】:

【参考方案12】:

你可以试试CsvHelper(我维护的一个库),它可以通过NuGet 获得。它遵循 CSV 的 RFC 4180 标准。它将能够处理字段内的任何内容,包括逗号、引号和换行符。

CsvHelper 使用简单,但也很容易配置它以处理许多不同类型的分隔文件。

CsvReader csv = new CsvReader( streamToFile );
IEnumerable<MyObject> myObjects = csv.GetRecords<MyObject>();

如果要读取较低级别的CSV文件,可以直接使用解析器,它将每一行作为字符串数组返回。

var parser = new CsvParser( myTextReader );
while( true )

    string[] line = parser.ReadLine();
    if( line == null )
    
        break;
    

【讨论】:

另外,它是迄今为止我见过的最快的通用 csv 解析器。

以上是关于在 .NET 中解析分隔的 CSV的主要内容,如果未能解决你的问题,请参考以下文章

iOS-解析读取CSV文件,解析excel文件

字符串 CSV解析 表格 逗号分隔值

你如何用 awk 解析逗号分隔值(csv)?

如何从字符向量中解析 CSV 数据以提取数据框?

用 Python 解析 CSV / 制表符分隔的 txt 文件

pyparsing用分号而不是逗号解析csv文件