2009-10-09 29 views

Respuesta

26

Aquí, escrito por los tuyos para utilizar colecciones genéricas y bloques de iteradores. Es compatible con campos de texto adjuntos de comillas dobles (incluidos los que abarcan varias líneas) utilizando la convención de doble escape (por lo que "" dentro de un campo entre comillas se lee como carácter de comillas simples). No es compatible con:

  • comilla simple texto encerrado
  • \ -escape al texto de la cita
  • delimitadores alternativos (sin embargo, no va a funcionar en el tubo o campos delimitados por tabuladores)
  • campos de texto que comienzan Unquoted con una cotización

Pero todas ellas serían lo suficientemente fáciles de agregar si las necesita. No lo he comparado en ninguna parte (me gustaría ver algunos resultados), pero el rendimiento debería ser muy bueno - mejor que cualquier otro que sea .Split() basado de todos modos.

Now on GitHub

actualización: se sentía como la adición de soporte texto entre comilla simple. Es un cambio simple, pero lo escribí directamente en la ventana de respuesta para que no haya sido probado. Use el enlace de revisión en la parte inferior si prefiere el código anterior (probado).

public static class CSV 
{ 
    public static IEnumerable<IList<string>> FromFile(string fileName) 
    { 
     foreach (IList<string> item in FromFile(fileName, ignoreFirstLineDefault)) yield return item; 
    } 

    public static IEnumerable<IList<string>> FromFile(string fileName, bool ignoreFirstLine) 
    { 
     using (StreamReader rdr = new StreamReader(fileName)) 
     { 
      foreach(IList<string> item in FromReader(rdr, ignoreFirstLine)) yield return item; 
     } 
    } 

    public static IEnumerable<IList<string>> FromStream(Stream csv) 
    { 
     foreach (IList<string> item in FromStream(csv, ignoreFirstLineDefault)) yield return item; 
    } 

    public static IEnumerable<IList<string>> FromStream(Stream csv, bool ignoreFirstLine) 
    { 
     using (var rdr = new StreamReader(csv)) 
     { 
      foreach (IList<string> item in FromReader(rdr, ignoreFirstLine)) yield return item; 
     } 
    } 

    public static IEnumerable<IList<string>> FromReader(TextReader csv) 
    { 
     //Probably should have used TextReader instead of StreamReader 
     foreach (IList<string> item in FromReader(csv, ignoreFirstLineDefault)) yield return item; 
    } 

    public static IEnumerable<IList<string>> FromReader(TextReader csv, bool ignoreFirstLine) 
    { 
     if (ignoreFirstLine) csv.ReadLine(); 

     IList<string> result = new List<string>(); 

     StringBuilder curValue = new StringBuilder(); 
     char c; 
     c = (char)csv.Read(); 
     while (csv.Peek() != -1) 
     { 
      switch (c) 
      { 
       case ',': //empty field 
        result.Add(""); 
        c = (char)csv.Read(); 
        break; 
       case '"': //qualified text 
       case '\'': 
        char q = c; 
        c = (char)csv.Read(); 
        bool inQuotes = true; 
        while (inQuotes && csv.Peek() != -1) 
        { 
         if (c == q) 
         { 
          c = (char)csv.Read(); 
          if (c != q) 
           inQuotes = false; 
         } 

         if (inQuotes) 
         { 
          curValue.Append(c); 
          c = (char)csv.Read(); 
         } 
        } 
        result.Add(curValue.ToString()); 
        curValue = new StringBuilder(); 
        if (c == ',') c = (char)csv.Read(); // either ',', newline, or endofstream 
        break; 
       case '\n': //end of the record 
       case '\r': 
        //potential bug here depending on what your line breaks look like 
        if (result.Count > 0) // don't return empty records 
        { 
         yield return result; 
         result = new List<string>(); 
        } 
        c = (char)csv.Read(); 
        break; 
       default: //normal unqualified text 
        while (c != ',' && c != '\r' && c != '\n' && csv.Peek() != -1) 
        { 
         curValue.Append(c); 
         c = (char)csv.Read(); 
        } 
        result.Add(curValue.ToString()); 
        curValue = new StringBuilder(); 
        if (c == ',') c = (char)csv.Read(); //either ',', newline, or endofstream 
        break; 
      } 

     } 
     if (curValue.Length > 0) //potential bug: I don't want to skip on a empty column in the last record if a caller really expects it to be there 
      result.Add(curValue.ToString()); 
     if (result.Count > 0) 
      yield return result; 

    } 
    private static bool ignoreFirstLineDefault = false; 
} 
+1

¿Puede manejar comas dentro de la secuencia entrecomillada? "me gusta, esto" ... ¿y puede manejar los retornos de carro en cadenas entrecomilladas? ... esas son algunas de las cosas que tienden a causar problemas ... – codeulike

+0

Sí, puede manejar ambos. Ese es el punto de las cadenas entre comillas. –

+1

Todavía me gusta esto, pero si tuviera que hacerlo probablemente heredaría TextReader –

8

Me gusta mucho la biblioteca FileHelpers. Es rápido, es C# 100%, está disponible para GRATIS, es muy flexible y fácil de usar.

+1

El asistente de FileHelpers parece realmente útil para crear clases estándar rápidamente. –

20

El last time this question was asked, aquí está the answer di:

Si sólo está tratando de leer un archivo CSV con C#, lo más fácil es utilizar la clase Microsoft.VisualBasic.FileIO.TextFieldParser. En realidad está integrado en .NET Framework, en lugar de ser una extensión de terceros.

Sí, está en Microsoft.VisualBasic.dll, pero eso no significa que no pueda usarlo desde C# (o cualquier otro lenguaje CLR).

Aquí hay un ejemplo de uso, tomada de la MSDN documentation:

Using MyReader As New _ 
Microsoft.VisualBasic.FileIO.TextFieldParser("C:\testfile.txt") 
    MyReader.TextFieldType = FileIO.FieldType.Delimited 
    MyReader.SetDelimiters(",") 
    Dim currentRow As String() 
    While Not MyReader.EndOfData 
     Try 
     currentRow = MyReader.ReadFields() 
     Dim currentField As String 
     For Each currentField In currentRow 
      MsgBox(currentField) 
     Next 
     Catch ex As Microsoft.VisualBasic.FileIO.MalformedLineException 
     MsgBox("Line " & ex.Message & _ 
     "is not valid and will be skipped.") 
     End Try 
    End While 
End Using 

Una vez más, este ejemplo es en VB.NET, pero sería trivial para traducirlo a C#.

+1

+1 No sabía de esta clase pero funciona muy bien. –

+0

He usado esta clase varias veces y la recomendaría. Integrarse en .NET significa que no tiene que preocuparse por los problemas de licencia/distribución. –

3

Además del análisis/lectura, algunas bibliotecas hacen otras cosas agradables como convertir los datos analizados en objetos para usted.

Aquí hay un ejemplo del uso de CsvHelper (una biblioteca que mantengo) para leer un archivo CSV en objetos.

var csv = new CsvHelper(File.OpenRead("file.csv")); 
var myCustomObjectList = csv.Reader.GetRecords<MyCustomObject>(); 

De manera predeterminada, las convenciones se utilizan para hacer coincidir los encabezados/columnas con las propiedades.Puede cambiar el comportamiento cambiando la configuración.

// Using attributes: 
public class MyCustomObject 
{ 
    [CsvField(Name = "First Name")] 
    public string StringProperty { get; set; } 

    [CsvField(Index = 0)] 
    public int IntProperty { get; set; } 

    [CsvField(Ignore = true)] 
    public string ShouldIgnore { get; set; } 
} 

A veces no "posee" el objeto con el que desea rellenar los datos. En este caso, puede usar un mapeo de clase fluido.

// Fluent class mapping: 
public sealed class MyCustomObjectMap : CsvClassMap<MyCustomObject> 
{ 
    public MyCustomObjectMap() 
    { 
     Map(m => m.StringProperty).Name("First Name"); 
     Map(m => m.IntProperty).Index(0); 
     Map(m => m.ShouldIgnore).Ignore(); 
    } 
} 
+0

Lo que no entiendo de la documentación es, ¿cuándo uso el mapeo y cuándo no debería usar el mapeo? Digamos que solo quiero analizar un archivo xml y palabras clave adicionales y sus datos adjuntos. ¿Necesito usar un mapa para eso? –

+0

Si está analizando XML, no debe usar una biblioteca CSV para hacerlo. La asignación de atributos ya no existe desde 2.0. La razón por la que usaría un archivo de asignación es si desea cambiar alguna de las formas predeterminadas en que la biblioteca lee el archivo. Una asignación le dará mucho control sobre cómo se lee/escribe el archivo. –

+0

Opps, eso fue un error ... Quise decir * CSV por supuesto. El mapeo no existe en .NET 4.0/5? Además, en esa nota, ¿qué recomendaría que use en su lugar para analizar CSV y tomar solo ciertas palabras clave y sus datos? –

4

Estoy implementando la respuesta de Daniel Pryden en C#, por lo que es más fácil de cortar y pegar y personalizar. Creo que este es el método más fácil para analizar archivos CSV. Solo agrega una referencia y básicamente has terminado.

Añadir la Microsoft.VisualBasic referencia a su proyecto

Entonces aquí es código de ejemplo en C# de la respuesta de Joel:

using (Microsoft.VisualBasic.FileIO.TextFieldParser MyReader = new   
     Microsoft.VisualBasic.FileIO.TextFieldParser(filename)) 
{ 
    MyReader.TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited; 
    MyReader.SetDelimiters(","); 

    while (!MyReader.EndOfData) 
    { 
     try 
     { 
      string[] fields = MyReader.ReadFields(); 
      if (first) 
      { 
       first = false; 
       continue; 
      } 

      // This is how I treat my data, you'll need to throw this out. 

      //"Type" "Post Date" "Description" "Amount" 
      LineItem li = new LineItem(); 

      li.date  = DateTime.Parse(fields[1]); 
      li.description = fields[2]; 
      li.Value  = Convert.ToDecimal(fields[3]); 

      lineitems1.Add(li); 
     } 
     catch (Microsoft.VisualBasic.FileIO.MalformedLineException ex) 
     { 
      MessageBox.Show("Line " + ex.Message + 
          " is not valid and will be skipped."); 
     } 
    } 
} 
1

Puede utilizar Microsoft.VisualBasic.FileIO.TextFieldParser

get ejemplo de código siguiente del artículo anterior

static void Main() 
     { 
      string [email protected]"C:\Users\Administrator\Desktop\test.csv"; 

      DataTable csvData = GetDataTabletFromCSVFile(csv_file_path); 

      Console.WriteLine("Rows count:" + csvData.Rows.Count); 

      Console.ReadLine(); 
     } 


private static DataTable GetDataTabletFromCSVFile(string csv_file_path) 
     { 
      DataTable csvData = new DataTable(); 

      try 
      { 

      using(TextFieldParser csvReader = new TextFieldParser(csv_file_path)) 
       { 
        csvReader.SetDelimiters(new string[] { "," }); 
        csvReader.HasFieldsEnclosedInQuotes = true; 
        string[] colFields = csvReader.ReadFields(); 
        foreach (string column in colFields) 
        { 
         DataColumn datecolumn = new DataColumn(column); 
         datecolumn.AllowDBNull = true; 
         csvData.Columns.Add(datecolumn); 
        } 

        while (!csvReader.EndOfData) 
        { 
         string[] fieldData = csvReader.ReadFields(); 
         //Making empty value as null 
         for (int i = 0; i < fieldData.Length; i++) 
         { 
          if (fieldData[i] == "") 
          { 
           fieldData[i] = null; 
          } 
         } 
         csvData.Rows.Add(fieldData); 
        } 
       } 
      } 
      catch (Exception ex) 
      { 
      } 
      return csvData; 
     }