解析用C#中的引号括起来的CSV文件

Lea*_*ary 2 c# csv

我在解析CSV文件时看到了很多样本​​.但这个有点烦人的档案......

那么你如何解析这种CSV

"1",2010年1月2日,"样本("adasdad")asdada","我在门口"Stinky",所以我会该死的","AK"

ruf*_*fin 5

在大多数情况下,最好的答案可能是@Jim Mischel.TextFieldParser对于大多数传统案例来说,似乎正是你想要的 - 虽然它奇怪地存在于Microsoft.VisualBasic命名空间中! 但这种情况并不常见.

我最后一次遇到这个问题的变化,我需要一些非常规的东西,我尴尬地放弃了regexp'ing并通过char检查剔除了一个char.有时,这不足以做到.如果你按字节推送,拆分字符串并不是一个问题.

所以我重写了这个案例作为字符串扩展.我认为这很接近.

请注意,这"I was pooping in the door "Stinky", so I'll be damn",是一个特别令人讨厌的案例.如果没有*** STINKY CONDITION ***下面的代码,您将获得I was pooping in the door "Stinky一个值和另一个值so I'll be damn".

对于任何匿名奇怪的分离器/转义情况,唯一的方法是使用某种算法来确定每行中"通常"的列数,然后在这种情况下检查固定长度字段,如您的AK州条目或其他一些可能的里程碑,作为不符合规范的列的一种规范化后备.但这可能是不需要的严重的疯狂逻辑,就像编码一样有趣.正如@Vash所指出的那样,你最好更好地遵循一些标准和编码更多的进攻性.

但这里的问题可能比这更容易. 唯一具有词汇意义的案例是你的例子中的一个",- 双引号,逗号,然后是空格.这就是*** STINKY CONDITION ***代码检查的内容.即便如此,这段代码变得比我想象的还要糟糕,这意味着你有一些奇怪的边缘情况,比如"This is also stinky," a f a b","Now what?" Heck,即使现在"A,"B","C"也没有在这段代码中工作,iirc,因为我把开始和结束字符视为已经逃脱前后固定.所以我们很大程度上回到了@Vash的评论!

为一行if陈述的所有括号道歉,但我现在陷入了一个StyleCop世界.我不一定建议你使用它 - strictEscapeToSplitEvaluation加上STINKY CONDITION使这有点复杂.但是值得注意的是,一个普通的csv解析器,它对于引用是明智的,直到变得单调乏味,但在其他方面是微不足道的.

namespace YourFavoriteNamespace 
{
    using System;
    using System.Collections.Generic;
    using System.Text;

    public static class Extensions
    {
        public static Queue<string> SplitSeeingQuotes(this string valToSplit, char splittingChar = ',', char escapeChar = '"', 
            bool strictEscapeToSplitEvaluation = true, bool captureEndingNull = false)
        {
            Queue<string> qReturn = new Queue<string>();
            StringBuilder stringBuilder = new StringBuilder();

            bool bInEscapeVal = false;

            for (int i = 0; i < valToSplit.Length; i++)
            {
                if (!bInEscapeVal)
                {
                    // Escape values must come immediately after a split.
                    // abc,"b,ca",cab has an escaped comma.
                    // abc,b"ca,c"ab does not.
                    if (escapeChar == valToSplit[i] && (!strictEscapeToSplitEvaluation || (i == 0 || (i != 0 && splittingChar == valToSplit[i - 1]))))
                    {
                        bInEscapeVal = true;    // not capturing escapeChar as part of value; easy enough to change if need be.
                    }
                    else if (splittingChar == valToSplit[i])
                    {
                        qReturn.Enqueue(stringBuilder.ToString());
                        stringBuilder = new StringBuilder();
                    }
                    else
                    {
                        stringBuilder.Append(valToSplit[i]);
                    }
                }
                else
                {
                    // Can't use switch b/c we're comparing to a variable, I believe.
                    if (escapeChar == valToSplit[i])
                    {
                        // Repeated escape always reduces to one escape char in this logic.
                        // So if you wanted "I'm ""double quote"" crazy!" to come out with 
                        // the double double quotes, you're toast.
                        if (i + 1 < valToSplit.Length && escapeChar == valToSplit[i + 1])
                        {
                            i++;
                            stringBuilder.Append(escapeChar);
                        }
                        else if (!strictEscapeToSplitEvaluation)
                        {
                            bInEscapeVal = false;
                        }
                        // *** STINKY CONDITION ***  
                        // Kinda defense, since only `", ` really makes sense.
                        else if ('"' == escapeChar && i + 2 < valToSplit.Length &&
                            valToSplit[i + 1] == ',' && valToSplit[i + 2] == ' ')
                        {
                            i = i+2;
                            stringBuilder.Append("\", ");
                        }
                        // *** EO STINKY CONDITION ***  
                        else if (i+1 == valToSplit.Length || (i + 1 < valToSplit.Length && valToSplit[i + 1] == splittingChar))
                        {
                            bInEscapeVal = false;
                        }
                        else
                        {
                            stringBuilder.Append(escapeChar);
                        }
                    }
                    else
                    {
                        stringBuilder.Append(valToSplit[i]);
                    }
                }
            }

            // NOTE: The `captureEndingNull` flag is not tested.
            // Catch null final entry?  "abc,cab,bca," could be four entries, with the last an empty string.
            if ((captureEndingNull && splittingChar == valToSplit[valToSplit.Length-1]) || (stringBuilder.Length > 0))
            {
                qReturn.Enqueue(stringBuilder.ToString());
            }

            return qReturn;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

可能值得一提的是,你给自己"答案"在其示例字符串中没有"Stinky"问题.; ^)

[了解我们问你们三年后],我会说你们的例子并不像这里的人们那样疯狂.我可以看到想要将转义字符(在本例中")作为转义字符处理,只有当它们是拆分字符后的第一个值时,或者在找到一个开放转义后,只有在分割器之前找到转义字符时才停止 ; 在这种情况下,分离器显然是,.

如果您的CSV的行abc,bc"a,ca"b,我希望这意味着我们有三个值:abc,bc"a,和ca"b.

您的"The sample ("adasdad") asdada"列中的相同交易- 不开始和结束单元格值的引号不是转义字符,并且不一定需要加倍才能保持含义.所以我strictEscapeToSplitEvaluation在这里添加了一面旗帜.

请享用.; ^)