Parse Delimited CSV en .NET

Tengo un archivo de texto que está en un formato separado por comas, delimitado por " en la mayoría de los campos. Estoy tratando de incluirlo en algo que pueda enumerar a través de (Colección Generic, por ejemplo). No tengo control sobre cómo el archivo es la salida ni el carácter que utiliza para el delimitador.

En este caso, los campos están separados por una coma y los campos de texto están encerrados en " marcas. El problema al que me estoy enfrentando es que algunos campos tienen comillas en ellos (es decir, Bandeja de 8 " ) y se recogen accidentalmente como el siguiente campo. En el caso de los campos numéricos, no tienen comillas a su alrededor, pero comienzan con un signo + o un signo (que representa un número positivo / negativo).

Estaba pensando en un RegEx, pero mis habilidades no son tan buenas, así que espero que alguien pueda aportar algunas ideas que pueda probar. Hay aproximadamente 19,000 registros en este archivo, por lo que estoy tratando de hacerlo de la manera más eficiente posible. Aquí hay un par de filas de datos de ejemplo:

 "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 

Hay muchos más campos, pero puede hacerse una idea …

Estoy usando VB.NET y tengo una configuración de lista genérica para aceptar los datos. He intentado usar CSVReader y parece funcionar bien hasta que tocas un registro como el tercero (con una cita en el campo de texto). Si de alguna manera puedo hacer que maneje las cotizaciones adicionales, la opción CSVReader funcionará bien.

¡Gracias!

Desde aquí :

 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 

Recomiendo mirar el TextFieldParserClass en .Net. Debes incluir

 Imports Microsoft.VisualBasic.FileIO.TextFieldParser 

Aquí hay una muestra rápida:

  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 

Prueba este sitio http://kbcsv.codeplex.com/

He buscado una buena utilidad y esta es sin dudas la mejor que he encontrado y funciona correctamente. No pierdas el tiempo probando otras cosas, esto es gratis y funciona.

Como este enlace dice … ¡No hagas rodar tu propio analizador CSV!

Use TextFieldParser como sugirió Avi. Microsoft ya ha hecho esto por ti. Si terminó escribiendo uno, y encuentra un error en él, considere reemplazarlo en lugar de corregir el error. Hice eso recientemente y me ahorró mucho tiempo.

Eche un vistazo a la biblioteca FileHelpers .

Puedes probar CsvHelper (una biblioteca que mantengo) y está disponible a través de NuGet . Sigue el estándar RFC 4180 para CSV. Podrá manejar cualquier contenido dentro de un campo que incluya comas, comillas y nuevas líneas.

CsvHelper es simple de usar, pero también es fácil de configurar para que funcione con muchos tipos diferentes de archivos delimitados.

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

Si desea leer archivos CSV en un nivel inferior, puede usar el analizador directamente, que devolverá cada fila como una matriz de cadenas.

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

Estoy publicando esto como una respuesta para poder explicar cómo lo hice y por qué … La respuesta de Mitch Wheat fue la que me dio la mejor solución para este caso y solo tuve que modificarla un poco debido al formato esta información fue exportada en.

Aquí está el Código VB:

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

El RegEx que se utilizó es lo que necesitaba cambiar porque ciertos campos tenían comillas sin escape y el RegEx proporcionado no parecía funcionar en todos los ejemplos. Este utiliza 'Mira hacia adelante' y 'Mira detrás' para ver si la cita es justo después de una coma o justo antes. En este caso, ambos son negativos (es decir, muéstreme dónde la comilla doble no está antes o después de una coma). Esto debería significar que la cita está en el medio de una cadena.

En este caso, en lugar de hacer un reemplazo directo, estoy usando la función ReplaceQuotes para manejar eso por mí. La razón por la que estoy usando esto es porque necesitaba un poco de lógica extra para detectar si estaba al principio de una línea. Si hubiera dedicado aún más tiempo, estoy seguro de que podría haber ajustado el RegEx para tener en cuenta el comienzo de la línea (usando MultiLine, etc.) pero cuando lo intenté rápidamente, no pareció funcionar en todas.

Con esto en su lugar, usando el lector CSV en un archivo CSV de 32MB (aproximadamente 19000 filas), toma aproximadamente 2 segundos leer el archivo, realizar la expresión regular, cargarla en el Lector CSV, agregar todos los datos a mi clase genérica y finalizar . ¡¡Muy rápido!!

RegEx para excluir la primera y última cotización sería (? . Por supuesto, necesita usar RegexOptions.Multiline.

De esta forma no hay necesidad de la función del evaluador. Mi código reemplaza las comillas dobles no deseadas con comillas simples.

El código completo de C # es el siguiente.

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

Hay al menos controladores ODBC para archivos CSV. Pero hay diferentes sabores de CSV.

¿Qué produjo estos archivos? No es poco probable que haya un controlador que coincida con los requisitos de la aplicación de origen.

Su problema con CSVReader es que la cita en el tercer registro no se escapa con otra cita (también conocida como comillas dobles). Si no los escapa, ¿cómo esperaría manejarlos “, en el medio de un campo de texto?

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

(Terminé trabajando con archivos (con diferentes delimitadores) pero los caracteres de comillas dentro de un valor de texto no se escaparon y terminé escribiendo mi propio analizador personalizado. No sé si esto fue absolutamente necesario o no).

La lógica de este enfoque personalizado es: leer el archivo 1 línea a la vez, dividir cada línea en la coma, eliminar el primer y el último carácter (eliminar las comillas externas pero sin afectar las comillas internas) y luego agregar los datos a su genérico lista. Es corto y muy fácil de leer y trabajar.

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