Como dividir csv cujas colunas podem conter,

105

Dado

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

Como usar C # para dividir as informações acima em strings da seguinte maneira:

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

Como você pode ver, uma das colunas contém, <= (Corvallis, OR)

// update // Baseado em C # Regex Split - vírgulas fora das aspas

string[] result = Regex.Split(samplestring, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
q0987
fonte
1
Embora em Java, pergunta semelhante: stackoverflow.com/questions/1757065/…
sgokhales
1
Usar uma regex para fazer isso é um conselho ruim. O .NET Framework já tem suporte integrado para analisar CSV. Veja esta resposta que você deve aceitar. Caso contrário, fecharei isso como uma cópia de stackoverflow.com/questions/3147836/… que é igualmente errado.
Kev
Você pode explicar qual é o suporte embutido do .NET para a análise de arquivos CSV com vírgulas embutidas? Você está se referindo à classe Microsoft.VisualBasic.FileIO.TextFieldParser?
AllSolutions

Respostas:

182

Use a Microsoft.VisualBasic.FileIO.TextFieldParserclasse. Isso tratará da análise de um arquivo delimitado, TextReaderou Streamonde alguns campos estão entre aspas e outros não.

Por exemplo:

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();

Isso deve resultar na seguinte saída:

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

Consulte Microsoft.VisualBasic.FileIO.TextFieldParser para obter mais informações.

Você precisa adicionar uma referência a Microsoft.VisualBasicna guia Add References .NET.

Tim
fonte
9
Cara, muito obrigado por essa solução, tenho cerca de 500K + linhas de dados CSV que preciso carregar em uma tabela e ela carregada com vírgulas contidas entre aspas. Devo a você uma bebida adulta de sua escolha, se nossos caminhos se cruzarem.
Mark Kram
@tim i usou isso e nota que ele está pulando todos os números de linha pares, processando apenas os números de linha ímpar em um arquivo que tem 1050 linhas. alguma ideia?
Smith
@Smith - Sem ver seu código ou entrada de amostra, não tenho ideia. Eu sugiro postar uma nova pergunta. Talvez o arquivo esteja sem um retorno de carro ou outro marcador de fim de linha nas linhas pares?
Tim
Eu nem sabia sobre essa biblioteca até ver isso - obrigado! Se alguém quiser um exemplo que analise um arquivo CSV inteiro, veja esta resposta do SO: stackoverflow.com/a/3508572/3105807
Amy Barrett
2
Podemos linchar a Microsoft por não fornecer um construtor que receba uma string, então temos que pular o aro de convertê-la em um fluxo primeiro? Caso contrário, boa resposta.
Loren Pechtel
43

É muito tarde, mas isso pode ser útil para alguém. Podemos usar RegEx como abaixo.

Regex CSVParser = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");
String[] Fields = CSVParser.Split(Test);
Husen
fonte
4
Isto é perfeito. Prefiro usar isso do que importar uma outra biblioteca inteira. Bravo.
TheGeekYouNeed
1
Corresponde a asdf, "", "as ,\" df ",
Esta solução não funciona corretamente - ela não leva em consideração as marcas de fala, o que significa que haverá muitas marcas de fala em locais incorretos durante a leitura.
AidanH
E se a citação final estiver faltando em alguma linha: asd, "", "as, \" df "," asd asd "," as
MarmiK
1
Isso funcionou para mim e foi responsável por aspas. 30 milhões de linhas deles. Muito bom e com uma quantidade mínima de código.
GBGOLC
4

Vejo que se você colar um texto delimitado por csv no Excel e fizer um "Texto para colunas", ele solicitará um "qualificador de texto". É padronizado para aspas duplas para que ele trate o texto entre aspas duplas como literal. Eu imagino que o Excel implementa isso indo um caractere por vez, se ele encontra um "qualificador de texto", ele continua indo para o próximo "qualificador". Você provavelmente pode implementar isso sozinho com um loop for e um booleano para denotar se estiver dentro de um texto literal.

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();
}
Roly
fonte
3

Use uma biblioteca como o LumenWorks para fazer a leitura de CSV. Ele lidará com campos com aspas e provavelmente será mais robusto do que sua solução personalizada, porque já existe há muito tempo.

Adam Lear
fonte
2

É uma questão complicada analisar arquivos .csv quando o arquivo .csv pode ser strings separadas por vírgulas, strings entre aspas separadas por vírgulas ou uma combinação caótica dos dois. A solução que encontrei permite qualquer uma das três possibilidades.

Criei um método, ParseCsvRow () que retorna uma matriz de uma string csv. Eu primeiro lido com aspas duplas na string dividindo a string entre aspas duplas em uma matriz chamada quotesArray. Arquivos .csv de string entre aspas só são válidos se houver um número par de aspas duplas. As aspas duplas em um valor de coluna devem ser substituídas por um par de aspas duplas (esta é a abordagem do Excel). Contanto que o arquivo .csv atenda a esses requisitos, você pode esperar que as vírgulas do delimitador apareçam apenas fora dos pares de aspas duplas. As vírgulas dentro de pares de aspas duplas fazem parte do valor da coluna e devem ser ignoradas ao dividir o .csv em uma matriz.

Meu método testará as vírgulas fora dos pares de aspas duplas, observando apenas os índices pares do quotesArray. Ele também remove aspas duplas do início e do final dos valores da coluna.

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

Uma desvantagem de minha abordagem é a maneira como substituo temporariamente as vírgulas delimitadoras por um caractere Unicode obscuro. Este caractere precisa ser tão obscuro que nunca aparecerá no seu arquivo .csv. Você pode querer colocar mais controle sobre isso.

Jason Williams
fonte
1

Eu tive um problema com um CSV que contém campos com um caractere de aspas, então usando o TextFieldParser, eu vim com o seguinte:

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

Um StreamReader ainda é usado para ler o CSV linha por linha, da seguinte maneira:

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

Com Cinchoo ETL - uma biblioteca de código aberto, ele pode manipular automaticamente os valores das colunas contendo separadores.

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

Resultado:

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

Para obter mais informações, visite o artigo codeproject.

Espero que ajude.

RajN
fonte