如何分割其列可能包含的csv,


105

给定

2,1016,7 /2008分之3114:22,杰夫达尔加斯6 /二千零十一分之五22:21,http://stackoverflow.com, “科瓦利斯,OR”,7679,351,81,b437f461b3fd27387c5d8ab47a293d35,34

如何使用C#将上述信息分成字符串,如下所示:

2
1016
7/31/2008 14:22
Geoff Dalgas
6/5/2011 22:21
http://stackoverflow.com
Corvallis, OR
7679
351
81
b437f461b3fd27387c5d8ab47a293d35
34

如您所见,其中一列包含,<=(科瓦利斯,俄勒冈州)

//更新//基于 C#正则表达式拆分-引号外的逗号

string[] result = Regex.Split(samplestring, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");

1
虽然在Java中,类似的问题:stackoverflow.com/questions/1757065/...
sgokhales

1
使用正则表达式执行此操作是不好的建议。.NET Framework已经内置了解析CSV的支持。请参阅此答案,这是您应该接受的答案。否则,我将其作为对stackoverflow.com/questions/3147836/…的欺骗将其关闭,这同样是错误的。
Kev

您能否详细说明.NET对带有嵌入式逗号的CSV文件进行解析的内置支持?您是否在引用Microsoft.VisualBasic.FileIO.TextFieldParser类?
AllSolutions 16/09/24

Answers:


182

使用Microsoft.VisualBasic.FileIO.TextFieldParser该类。这将处理解析定界文件,TextReader或者Stream某些字段用引号引起来,而有些则没有。

例如:

using Microsoft.VisualBasic.FileIO;

string csv = "2,1016,7/31/2008 14:22,Geoff Dalgas,6/5/2011 22:21,http://stackoverflow.com,\"Corvallis, OR\",7679,351,81,b437f461b3fd27387c5d8ab47a293d35,34";

TextFieldParser parser = new TextFieldParser(new StringReader(csv));

// You can also read from a file
// TextFieldParser parser = new TextFieldParser("mycsvfile.csv");

parser.HasFieldsEnclosedInQuotes = true;
parser.SetDelimiters(",");

string[] fields;

while (!parser.EndOfData)
{
    fields = parser.ReadFields();
    foreach (string field in fields)
    {
        Console.WriteLine(field);
    }
} 

parser.Close();

这将导致以下输出:

2
1016
7/31/2008 14:22
杰夫·达尔加斯(Geoff Dalgas)
6/6/2011 22:21
http://stackoverflow.com
俄勒冈州科瓦利斯
7679
351
81
b437f461b3fd27387c5d8ab47a293d35
34

有关更多信息,请参见Microsoft.VisualBasic.FileIO.TextFieldParser

您需要Microsoft.VisualBasic在“添加引用.NET”选项卡中添加对的引用。


9
杜德(Dude),非常感谢您提供此解决方案,我需要将约50万行以上的CSV数据加载到表中,并用引号内的逗号加载。如果我们的道路交叉,我欠您选择的成人饮料。
马克·克拉姆

@tim我使用了它,并注意到它跳过所有偶数行号,仅处理具有1050行的文件中的奇数行号。有任何想法吗?
史密斯

@Smith-没有看到您的代码或示例输入,我不知道。我建议发布一个新问题。也许文件在偶数行上缺少回车符或其他行尾标记?
蒂姆(Tim)

我什至不知道这个图书馆,直到我看到了-谢谢!如果其他任何人都想要解析整个CSV文件的示例,请参见以下SO答案:stackoverflow.com/a/3508572/3105807
Amy Barrett

2
难道我们可以私自让Microsoft不提供采用字符串的构造函数,以便我们必须跳过将其首先转换为流的障碍吗?否则,很好的答案。
劳伦·佩希特尔

43

太晚了,但这对某人可能有所帮助。我们可以将RegEx用作波纹管。

Regex CSVParser = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");
String[] Fields = CSVParser.Split(Test);

4
太棒了。宁愿使用它,也不愿导入整个其他库。太棒了
TheGeekYouNeed '18

1
匹配asdf,“”,“ as ,\” df“,

该解决方案无法正常工作-它不能解决语音标记,这意味着在阅读过程中,错误位置会出现很多语音标记。
艾丹,

如果某些行中缺少结尾引号怎么办:asd,“”,“ as,\” df“,” asd asd“,” as
MarmiK

1
这对我有用,并解释了引用的语音标记。其中三千万行。很好,代码最少。
GBGOLC


4

我看到,如果您在Excel中粘贴csv分隔的文本并执行“文本到列”,它将要求您输入“文本限定符”。默认情况下使用双引号,以便将双引号内的文本视为文字。我想像一下,Excel通过一次移动一个字符来实现此功能,如果遇到一个“文本限定符”,它将继续进入下一个“限定符”。您可能自己可以使用for循环和一个布尔值来实现此目的,以表示您是否在文字文本中。

public string[] CsvParser(string csvText)
{
    List<string> tokens = new List<string>();

    int last = -1;
    int current = 0;
    bool inText = false;

    while(current < csvText.Length)
    {
        switch(csvText[current])
        {
            case '"':
                inText = !inText; break;
            case ',':
                if (!inText) 
                {
                    tokens.Add(csvText.Substring(last + 1, (current - last)).Trim(' ', ',')); 
                    last = current;
                }
                break;
            default:
                break;
        }
        current++;
    }

    if (last != csvText.Length - 1) 
    {
        tokens.Add(csvText.Substring(last+1).Trim());
    }

    return tokens.ToArray();
}

3

使用LumenWorks之类的库进行CSV阅读。它会处理带有引号的字段,并且由于存在很长一段时间,因此总体上可能会比自定义解决方案更健壮。


2

当.csv文件可能是逗号分隔的字符串,逗号分隔的带引号的字符串或两者的混乱组合时,解析.csv文件是一件棘手的事情。我提出的解决方案允许这三种可能性中的任何一种。

我创建了一个方法ParseCsvRow(),该方法从csv字符串返回一个数组。我首先通过将双引号上的字符串拆分为一个称为quotesArray的数组来处理字符串中的双引号。带引号的字符串.csv文件仅在双引号双数的情况下才有效。列值中的双引号应替换为一对双引号(这是Excel的方法)。只要.csv文件满足这些要求,您就可以预期分隔符逗号只会出现在双引号对之外。双引号对内的逗号是列值的一部分,将.csv拆分为数组时,应将其忽略。

我的方法将通过仅查看quotesArray的偶数索引来测试双引号对之外的逗号。它还从列值的开头和结尾删除双引号。

    public static string[] ParseCsvRow(string csvrow)
    {
        const string obscureCharacter = "ᖳ";
        if (csvrow.Contains(obscureCharacter)) throw new Exception("Error: csv row may not contain the " + obscureCharacter + " character");

        var unicodeSeparatedString = "";

        var quotesArray = csvrow.Split('"');  // Split string on double quote character
        if (quotesArray.Length > 1)
        {
            for (var i = 0; i < quotesArray.Length; i++)
            {
                // CSV must use double quotes to represent a quote inside a quoted cell
                // Quotes must be paired up
                // Test if a comma lays outside a pair of quotes.  If so, replace the comma with an obscure unicode character
                if (Math.Round(Math.Round((decimal) i/2)*2) == i)
                {
                    var s = quotesArray[i].Trim();
                    switch (s)
                    {
                        case ",":
                            quotesArray[i] = obscureCharacter;  // Change quoted comma seperated string to quoted "obscure character" seperated string
                            break;
                    }
                }
                // Build string and Replace quotes where quotes were expected.
                unicodeSeparatedString += (i > 0 ? "\"" : "") + quotesArray[i].Trim();
            }
        }
        else
        {
            // String does not have any pairs of double quotes.  It should be safe to just replace the commas with the obscure character
            unicodeSeparatedString = csvrow.Replace(",", obscureCharacter);
        }

        var csvRowArray = unicodeSeparatedString.Split(obscureCharacter[0]); 

        for (var i = 0; i < csvRowArray.Length; i++)
        {
            var s = csvRowArray[i].Trim();
            if (s.StartsWith("\"") && s.EndsWith("\""))
            {
                csvRowArray[i] = s.Length > 2 ? s.Substring(1, s.Length - 2) : "";  // Remove start and end quotes.
            }
        }

        return csvRowArray;
    }

我的方法的缺点之一是我用模糊的Unicode字符临时替换定界符逗号。该字符必须如此晦涩,以至于永远不会显示在您的.csv文件中。您可能需要对此进行更多处理。


1

CSV包含一个带引号字符的字段时,我遇到了问题,因此使用TextFieldParser,我想到了以下内容:

private static string[] parseCSVLine(string csvLine)
{
  using (TextFieldParser TFP = new TextFieldParser(new MemoryStream(Encoding.UTF8.GetBytes(csvLine))))
  {
    TFP.HasFieldsEnclosedInQuotes = true;
    TFP.SetDelimiters(",");

    try 
    {           
      return TFP.ReadFields();
    }
    catch (MalformedLineException)
    {
      StringBuilder m_sbLine = new StringBuilder();

      for (int i = 0; i < TFP.ErrorLine.Length; i++)
      {
        if (i > 0 && TFP.ErrorLine[i]== '"' &&(TFP.ErrorLine[i + 1] != ',' && TFP.ErrorLine[i - 1] != ','))
          m_sbLine.Append("\"\"");
        else
          m_sbLine.Append(TFP.ErrorLine[i]);
      }

      return parseCSVLine(m_sbLine.ToString());
    }
  }
}

StreamReader仍用于逐行读取CSV,如下所示:

using(StreamReader SR = new StreamReader(FileName))
{
  while (SR.Peek() >-1)
    myStringArray = parseCSVLine(SR.ReadLine());
}

1

使用Cinchoo ETL-一个开源库,它可以自动处理包含分隔符的列值。

string csv = @"2,1016,7/31/2008 14:22,Geoff Dalgas,6/5/2011 22:21,http://stackoverflow.com,""Corvallis, OR"",7679,351,81,b437f461b3fd27387c5d8ab47a293d35,34";

using (var p = ChoCSVReader.LoadText(csv)
    )
{
    Console.WriteLine(p.Dump());
}

输出:

Key: Column1 [Type: String]
Value: 2
Key: Column2 [Type: String]
Value: 1016
Key: Column3 [Type: String]
Value: 7/31/2008 14:22
Key: Column4 [Type: String]
Value: Geoff Dalgas
Key: Column5 [Type: String]
Value: 6/5/2011 22:21
Key: Column6 [Type: String]
Value: http://stackoverflow.com
Key: Column7 [Type: String]
Value: Corvallis, OR
Key: Column8 [Type: String]
Value: 7679
Key: Column9 [Type: String]
Value: 351
Key: Column10 [Type: String]
Value: 81
Key: Column11 [Type: String]
Value: b437f461b3fd27387c5d8ab47a293d35
Key: Column12 [Type: String]
Value: 34

有关更多信息,请访问codeproject文章。

希望能帮助到你。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.