Manejo de campos que contienen comillas dobles no escapadas con TextFieldParser

Estoy tratando de importar un archivo CSV usando TextFieldParser . Un archivo CSV en particular me está causando problemas debido a su formato no estándar. El CSV en cuestión tiene sus campos encerrados entre comillas dobles. El problema aparece cuando hay un conjunto adicional de comillas dobles sin escapar dentro de un campo en particular.

Aquí hay un caso de prueba simplificado que resalta el problema. No todos los archivos CSV reales con los que estoy tratando tienen el mismo formato y tienen docenas de campos, cualquiera de los cuales puede contener estos posibles problemas de formato.

TextReader reader = new StringReader("\"Row\",\"Test String\"\n" + "\"1\",\"This is a test string. It is parsed correctly.\"\n" + "\"2\",\"This is a test string with a comma, which is parsed correctly\"\n" + "\"3\",\"This is a test string with double \"\"double quotes\"\". It is parsed correctly\"\n" + "\"4\",\"This is a test string with 'single quotes'. It is parsed correctly\"\n" + "5,This is a test string with fields that aren't enclosed in double quotes. It is parsed correctly.\n" + "\"6\",\"This is a test string with single \"double quotes\". It can't be parsed.\""); using (TextFieldParser parser = new TextFieldParser(reader)) { parser.Delimiters = new[] { "," }; while (!parser.EndOfData) { string[] fields= parser.ReadFields(); Console.WriteLine("This line was parsed as:\n{0},{1}", fields[0], fields[1]); } } 

¿Hay alguna forma de analizar correctamente un CSV con este tipo de formato usando TextFieldParser?

Estoy de acuerdo con el consejo de Hans Passant de que no es su responsabilidad analizar los datos mal formados. Sin embargo, de acuerdo con el Principio de Robustez , alguien que se enfrenta a esta situación puede intentar manejar tipos específicos de datos mal formados. El código que escribí a continuación funciona en el conjunto de datos especificado en la pregunta. Básicamente, detecta el error del analizador en la línea con formato incorrecto, determina si se trata de comillas dobles basadas en el primer carácter, y luego divide / elimina manualmente todas las comillas dobles.

 using (TextFieldParser parser = new TextFieldParser(reader)) { parser.Delimiters = new[] { "," }; while (!parser.EndOfData) { string[] fields = null; try { fields = parser.ReadFields(); } catch (MalformedLineException ex) { if (parser.ErrorLine.StartsWith("\"")) { var line = parser.ErrorLine.Substring(1, parser.ErrorLine.Length - 2); fields = line.Split(new string[] { "\",\"" }, StringSplitOptions.None); } else { throw; } } Console.WriteLine("This line was parsed as:\n{0},{1}", fields[0], fields[1]); } } 

Estoy seguro de que es posible inventar un ejemplo patológico en el que esto falla (por ejemplo, comas adyacentes a comillas dobles dentro de un valor de campo), pero cualquier ejemplo de este tipo probablemente no se pueda analizar en el sentido más estricto, mientras que la línea de problemas que se presenta en la pregunta es Descifrable a pesar de estar malformado.

Puede ser más fácil hacer esto manualmente, y sin duda le dará más control:

Edición: para su ejemplo clarificado, todavía sugiero manejar manualmente el análisis:

 using System.IO; string[] csvFile = File.ReadAllLines(pathToCsv); foreach (string line in csvFile) { // get the first comma in the line // everything before this index is the row number // everything after is the row value int firstCommaIndex = line.IndexOf(','); //Note: SubString used here is (startIndex, length) string row = line.Substring(0, firstCommaIndex+1); string rowValue = line.Substring(firstCommaIndex+1).Trim(); Console.WriteLine("This line was parsed as:\n{0},{1}", row, rowValue); } 

Para un CSV genérico que no permite comas en los campos:

 using System.IO; string[] csvFile = File.ReadAllLines(pathToCsv); foreach (string line in csvFile) { string[] fields = line.Split(','); Console.WriteLine("This line was parsed as:\n{0},{1}", fields[0], fields[1]); } 

Solución de trabajo:

 using (TextFieldParser csvReader = new TextFieldParser(csv_file_path)) { csvReader.SetDelimiters(new string[] { "," }); csvReader.HasFieldsEnclosedInQuotes = false; string[] colFields = csvReader.ReadFields(); while (!csvReader.EndOfData) { string[] fieldData = csvReader.ReadFields(); for (i = 0; i < fieldData.Length; i++) { if (fieldData[i] == "") { fieldData[i] = null; } else { if (fieldData[i][0] == '"' && fieldData[i][fieldData[i].Length - 1] == '"') { fieldData[i] = fieldData[i].Substring(1, fieldData[i].Length - 2); } } } csvData.Rows.Add(fieldData); } } 

Si no establece HasFieldsEnclosedInQuotes = true, la lista resultante de columnas será más si los datos contienen una coma (,). por ejemplo, “Col1”, “Col2”, “Col3” “Test1”, 100, “Test1, Test2” “Test2”, 200, “Test22” Este archivo debe tener 3 columnas, pero al analizarlo obtendrá 4 campos, lo que es incorrecto.

La solución de Jordan es bastante buena, pero supone erróneamente que la línea de error siempre comenzará con una comilla doble. Mi línea de error fue la siguiente:

 170,"CMS ALT",853,,,NON_MOVEX,COM,NULL,"2014-04-25","" 204 Route de Trays" 

Observe que el último campo tenía comillas dobles adicionales / no escapadas, pero el primer campo estaba bien. Así que la solución de Jordan no funcionó. Aquí está mi solución modificada basada en la de Jordan:

 using(TextFieldParser parser = new TextFieldParser(new StringReader(csv))) { parser.Delimiters = new [] {","}; while (!parser.EndOfData) { string[] fields = null; try { fields = parser.ReadFields(); } catch (MalformedLineException ex) { string errorLine = SafeTrim(parser.ErrorLine); fields = errorLine.Split(','); } } } 

Es posible que desee manejar el bloque catch de manera diferente, pero el concepto general funciona muy bien para mí.

Por favor, establezca HasFieldsEnclosedInQuotes = true en el objeto TextFieldParser antes de comenzar a leer el archivo.