Analisar CSV Delimitado no .NET

Eu tenho um arquivo de texto que está em um formato separado por vírgula, delimitado por " na maioria dos campos. Eu estou tentando colocar isso em algo que eu possa enumerar através de (coleção genérica, por exemplo). Eu não tenho controle sobre como o arquivo é a saída nem o caractere usado para o delimitador.

Nesse caso, os campos são separados por uma vírgula e os campos de texto são colocados entre " marcas " . O problema que estou encontrando é que alguns campos possuem aspas (ou seja, 8 " Bandeja) e são acidentalmente capturados como próximos campo. No caso de campos numéricos, eles não têm aspas ao redor, mas começam com um sinal + ou um – (representando um número positivo / negativo).

Eu estava pensando em um RegEx, mas minhas habilidades não são tão boas, então espero que alguém possa ter algumas idéias que eu possa tentar. Existem cerca de 19.000 registros nesse arquivo, então estou tentando fazer isso da maneira mais eficiente possível. Aqui estão algumas linhas de dados de exemplo:

 "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 

Há muito mais campos, mas você pode obter a imagem ….

Estou usando o VB.NET e tenho uma configuração de lista genérica para aceitar os dados. Eu tentei usar CSVReader e parece funcionar bem até que você bateu um recorde como o terceiro (com uma citação no campo de texto). Se eu conseguisse, de alguma forma, lidar com as aspas adicionais, então a opção CSVReader funcionaria muito bem.

Obrigado!

A partir daqui :

 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 

Eu recomendo olhar para o TextFieldParserClass no .net. Você precisa include

 Imports Microsoft.VisualBasic.FileIO.TextFieldParser 

Aqui está um exemplo rápido:

  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 

Experimente este site. http://kbcsv.codeplex.com/

Eu procurei por um bom utilitário e este é o melhor que encontrei e funciona corretamente. Não perca seu tempo tentando outras coisas, isso é grátis e funciona.

Como este link diz … Não role seu próprio analisador de CSV!

Use TextFieldParser como Avi sugerido. A Microsoft já fez isso por você. Se você acabou escrevendo um, e você encontrar um bug nele, considere substituí-lo em vez de consertar o bug. Eu fiz exatamente isso recentemente e isso me poupou muito tempo.

Dê uma olhada na biblioteca FileHelpers .

Você poderia dar uma chance ao CsvHelper (uma biblioteca que eu mantenho) e está disponível via NuGet . Segue a norma RFC 4180 para CSV. Ele poderá manipular qualquer conteúdo dentro de um campo, incluindo vírgulas, cotações e novas linhas.

O CsvHelper é simples de usar, mas também é fácil configurá-lo para trabalhar com muitos tipos diferentes de arquivos delimitados.

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

Se você quiser ler arquivos CSV em um nível inferior, você pode usar o analisador diretamente, que retornará cada linha como uma matriz de string.

 var parser = new CsvParser( myTextReader ); while( true ) { string[] line = parser.ReadLine(); if( line == null ) { break; } } 

Estou postando isso como uma resposta para que eu possa explicar como eu fiz e porque …. A resposta do Mitch Wheat foi a que me deu a melhor solução para este caso e eu tive que modificá-lo levemente devido ao formato esses dados foram exportados em.

Aqui está o código VB:

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

O RegEx que foi usado é o que eu precisava mudar porque certos campos tinham citações não escapadas e o RegEx fornecido não parecia funcionar em todos os exemplos. Este usa "Look Ahead" e "Look Behind" para ver se a citação é apenas após uma vírgula ou apenas antes. Nesse caso, ambos são negativos (ou seja, mostre-me onde as aspas duplas não estão antes ou depois de uma vírgula). Isso deve significar que a cotação está no meio de uma string.

Nesse caso, em vez de fazer uma substituição direta, estou usando a function ReplaceQuotes para lidar com isso para mim. A razão pela qual estou usando isso é porque eu precisava de um pouco de lógica extra para detectar se estava no início de uma linha. Se eu tivesse gasto ainda mais tempo nisso, tenho certeza que poderia ter ajustado o RegEx para levar em consideração o início da linha (usando MultiLine, etc), mas quando eu tentei rapidamente, ele não parecia funcionar em todos.

Com isso, usando o CSV reader em um arquivo CSV de 32MB (cerca de 19.000 linhas), demora cerca de 2 segundos para ler o arquivo, executar o regex, carregá-lo no CSV Reader, adicionar todos os dados à minha class genérica e terminar . Realmente rápido!!

RegEx para excluir a primeira e última aspas seria (? . Claro, você precisa usar o RegexOptions.Multiline.

Dessa forma, não há necessidade de function de avaliador. Meu código substitui aspas duplas indesejadas por aspas simples.

O código C # completo é como abaixo.

 string fixedCSV = Regex.Replace( File.ReadAllText(fileName), @"(? 

Existem pelo menos drivers ODBC para arquivos CSV. Mas existem diferentes sabores de CSV.

O que produziu esses arquivos? Não é improvável que haja um driver correspondente com base nos requisitos do aplicativo de origem.

Seu problema com CSVReader é que a citação no terceiro registro não é escapada com outra citação (também chamada de citação dupla). Se você não escapa deles, então como você espera lidar “, no meio de um campo de texto?

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

(Eu acabei tendo que trabalhar com arquivos (com diferentes delimitadores), mas os caracteres de citação dentro de um valor de texto não foram ignorados e acabei escrevendo meu próprio analisador personalizado. Eu não sei se isso era absolutamente necessário ou não.)

A lógica dessa abordagem personalizada é: Leia a linha do arquivo 1 por vez, divida cada linha na vírgula, remova o primeiro e o último caractere (removendo as aspas externas, mas não afetando as aspas internas) e adicionando os dados ao seu genérico Lista. É curto e muito fácil de ler e trabalhar.

  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. 'ie UpdatedItems = Item. remove first and last character 'Then stick the data into your Generic List (Of String()?) Next End While 
  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; }