在.NET中解析分隔的CSV

时间:2009-04-10 03:22:17

标签: .net vb.net parsing csv

我有一个逗号分隔格式的文本文件,在大多数字段上由"分隔。我试图将其纳入我可以枚举的内容(例如,Generic Collection)。我无法控制文件的输出方式,也无法控制用于分隔符的字符。

在这种情况下,字段用逗号分隔,文本字段用"标记括起来。我遇到的问题是某些字段中包含引号(即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,我有一个通用的List设置来接受数据。我已经尝试使用CSVReader,它似乎运行良好,直到你达到像第三个记录(在文本字段中引用)的记录​​。如果我能以某种方式让它来处理额外的引号,那么CSVReader选项将会很好用。

谢谢!

12 个答案:

答案 0 :(得分:76)

我建议查看.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

答案 1 :(得分:11)

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

我已经找到了一个很好的实用程序,这是我找到并正常工作的最好的。不要浪费你的时间尝试其他东西,这是免费的,它的确有效。

答案 2 :(得分:7)

来自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

答案 3 :(得分:7)

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

使用TextFieldParser作为Avi建议。 Microsoft已经为您完成了此操作。如果您最终编写了一个,并且发现其中有错误,请考虑替换它而不是修复错误。我最近做了这件事,它给我节省了很多时间。

答案 4 :(得分:5)

答案 5 :(得分:5)

您可以尝试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;
    }
}

答案 6 :(得分:1)

我发布这个作为答案,所以我可以解释我是如何做到这一点以及为什么...... Mitch Wheat的答案给了我这个案例的最佳解决方案,我只需稍微修改它到这个数据导出的格式。

这是VB代码:

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

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

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

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

答案 7 :(得分:1)

RegEx排除第一个和最后一个引用将是(?<!^)(?<!,)("")(?!,)(?!$)。当然,您需要使用RegexOptions.Multiline。

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

完整的C#代码如下。

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

答案 8 :(得分:0)

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

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

答案 9 :(得分:0)

您对CSVReader的问题在于,第三条记录中的引号未使用其他引号(也称为双引号)进行转义。如果你没有逃避它们,那么你期望如何处理“,在文本字段的中间?

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

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

答案 10 :(得分:0)

这种自定义方法的逻辑是:一次读取文件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

答案 11 :(得分:0)

        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;
    }