Cómo dividir csv cuyas columnas pueden contener,

Dado

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ómo usar C # para dividir la información anterior en cadenas de la siguiente manera:

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 puede ver, una de las columnas contiene, <= (Corvallis, OR)

// update // Basado en C # Regex Split – comas fuera de las comillas

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

Use la clase Microsoft.VisualBasic.FileIO.TextFieldParser . Esto manejará el análisis de un archivo delimitado, TextReader o Stream donde algunos campos están entre comillas y otros no.

Por ejemplo:

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

Esto debería dar como resultado el siguiente resultado:

 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 obtener más información.

Debe agregar una referencia a Microsoft.VisualBasic en la pestaña Agregar referencias de .NET.

Podría dividir todas las comas que sí tienen un número par de citas siguiéndolas.

También le gustaría ver en el specf formato CSV sobre cómo manejar las comas.

Enlace útil: C# Regex Split - commas outside quotes

Es muy tarde pero esto puede ser útil para alguien. Podemos usar RegEx como abajo.

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

Veo que si pega texto csv delimitado en Excel y hace un “Texto a columnas”, le pide un “calificador de texto”. Está predeterminado a una comilla doble para que trate el texto entre comillas dobles como literales. Imagino que Excel implementa esto yendo un carácter a la vez, si encuentra un “calificador de texto”, sigue yendo al siguiente “calificador”. Probablemente pueda implementarlo usted mismo con un bucle for y un booleano para denotar si está dentro de un texto literal.

 public string[] CsvParser(string csvText) { List tokens = new List(); 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(); } 

Es una cuestión complicada analizar archivos .csv cuando el archivo .csv puede ser cadenas separadas por comas, cadenas entre comillas separadas por comas o una combinación caótica de las dos. La solución que se me ocurrió permite cualquiera de las tres posibilidades.

Creé un método, ParseCsvRow () que devuelve una matriz de una cadena csv. Primero trato con comillas dobles en la cadena dividiendo la cadena en comillas dobles en una matriz llamada quotesArray. Los archivos .csv de cadenas entrecomilladas solo son válidos si hay un número par de comillas dobles. Las comillas dobles en un valor de columna deben reemplazarse por un par de comillas dobles (este es el enfoque de Excel). Siempre que el archivo .csv cumpla con estos requisitos, puede esperar que las comas del delimitador aparezcan solo fuera de los pares de comillas dobles. Las comas dentro de los pares de comillas dobles son parte del valor de la columna y se deben ignorar al dividir el .csv en una matriz.

Mi método probará las comas fuera de los pares de comillas dobles mirando solo los índices de las cotizaciones Array. También elimina las comillas dobles de los valores de inicio y final de la columna.

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

Una desventaja de mi enfoque es la manera en que reemplazo temporalmente las comillas delimitadoras por un oscuro carácter Unicode. Este personaje debe ser tan oscuro que nunca aparecerá en tu archivo .csv. Es posible que desee poner más manejo al respecto.

Use una biblioteca como LumenWorks para hacer su lectura de CSV. Manejará los campos con comillas y, en general, será más robusto que su solución personalizada en virtud de haber estado presente por mucho tiempo.

Tuve un problema con un archivo CSV que contiene campos con un carácter de comillas, así que al usar TextFieldParser, se me ocurrió lo siguiente:

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

Un StreamReader todavía se usa para leer el CSV línea por línea, de la siguiente manera:

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

Con Cinchoo ETL , una biblioteca de código abierto, puede manejar automáticamente valores de columnas que contienen 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()); } 

Salida:

 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 obtener más información, visite el artículo de codeproject.

Espero eso ayude.