2011-07-01 44 views
59

Dado¿Cómo dividir csv cuyas columnas pueden contener,

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

Cómo utilizar 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 se puede ver uno de la columna contiene, < = (Corvallis, Oregón)

// // actualización Basado en C# Regex Split - commas outside quotes

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

Aunque en Java, pregunta similar: http://stackoverflow.com/questions/1757065/java-splitting-a-comma-separated-string-but-ignoring-commas-in -quotes –

+1

@saugok, lo siento señor, no lo es. – q0987

+0

Usar una expresión regular para hacer esto es un mal consejo. .NET Framework ya tiene soporte integrado para analizar CSV. Vea esta respuesta que es la que debe aceptar. De lo contrario voy a cerrar esto como una víctima de http://stackoverflow.com/questions/3147836/c-regex-split-commas-outside-quotes que es tan igual de mal. – Kev

Respuesta

131

Usa 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 debe resultar en la siguiente salida:

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

Ver Microsoft.VisualBasic.FileIO.TextFieldParser para más información.

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

+5

Amigo, muchas gracias por esta solución, que tiene unos 500 K + filas de datos CSV que tengo que cargar en una tabla y se carga con comas contenidas dentro de las comillas. Te debo una bebida para adultos de tu elección si nuestros caminos alguna vez se cruzan. –

+0

Muchas gracias !!!! Esto funciona excelente! – vldmrrdjcc

+0

@tim lo usé, y nota que omite todos los números de línea pares, solo procesa los números de líneas impares en un archivo que tiene 1050 líneas. ¿algunas ideas? – Smith

2

Usar una biblioteca como LumenWorks hacer su lectura 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.

4

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

También le gustaría ver en el specf para formato CSV sobre manejo de coma.

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

+3

@ q0987: esta no es la respuesta correcta. Se construye en apoyo para esto en el marco: http://stackoverflow.com/questions/6542996/how-to-split-csv-whose-columns-may-contain/6543418#6543418 – Kev

3

veo que si pega csv texto delimitado en Excel y hacer un "texto en columnas", le pregunte por 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<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(); 
} 
3

Es una cuestión complicada analizar archivos .csv cuando el archivo .csv puede ser cadenas separadas por comas, cadenas entre comas 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. Cuerda citada.Los archivos csv 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 pondrá a prueba por comas fuera de pares de comillas dobles mirando solamente incluso índices de la quotesArray. 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 de las desventajas de mi enfoque es la forma en que temporalmente sustituyo 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.

1

tuve un problema con un archivo CSV que contiene campos con un carácter de comillas en ellos, así que usar el TextFieldParser, 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 utiliza para leer la línea CSV por línea, de la siguiente manera:

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