2009-10-09 11 views

Antwort

26

Hier, von Ihnen geschrieben, um generische Sammlungen und Iterator-Blöcke zu verwenden. Es unterstützt doppelte Anführungszeichen eingeschlossene Textfelder (einschließlich solcher, die sich über mehrere Zeilen erstrecken) mit der Doppel-Escape-Konvention (so wird "" in einem Feld in Anführungszeichen als einfaches Anführungszeichen gelesen). Es unterstützt nicht:

  • Einzel-Zitat eingeschlossen Text
  • \ text
  • alternative Trennzeichen -escaped zitiert (wird noch nicht auf dem Rohr oder Tabulator getrennte Felder arbeiten)
  • Unquoted Textfelder, die beginnen mit einem Zitat

Aber alle von denen wäre leicht genug, um hinzuzufügen, wenn Sie sie brauchen. Ich habe es nirgends benchmarked (ich würde gerne einige Ergebnisse sehen), aber die Leistung sollte sehr gut sein - besser als alles, was .Split() sowieso basiert.

Now on GitHub

aktualisieren: fühlte sich wie einzelne Anführungszeichen eingeschlossen Text-Unterstützung hinzugefügt. Es ist eine einfache Änderung, aber ich tippte es direkt in das Antwortfenster, so dass es nicht getestet wurde. Verwenden Sie den Versionslink unten, wenn Sie den alten (getesteten) Code bevorzugen.

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

Kann es Kommas innerhalb der Zeichenfolge in Anführungszeichen behandeln? "like, this" ... und kann es in zitierten Strings mit Wagenrücklauf umgehen? ... das sind einige der Dinge, die dazu neigen, Ärger zu verursachen ... – codeulike

+0

Ja, es kann beides. Das ist der Sinn von Strings in Anführungszeichen. –

+1

Ich mag das immer noch, aber wenn ich es hätte tun müssen, würde ich wahrscheinlich TextReader erben –

8

Ich mag die FileHelpers Bibliothek. Es ist schnell, es ist C# 100%, es ist verfügbar für FREE, es ist sehr flexibel und einfach zu bedienen.

+1

Der FileHelpers Wizard sieht sehr nützlich beim schnellen Erstellen von Standard-Klassen. –

20

The last time this question was asked, hier ist the answer Ich gab:

Wenn Sie nur eine CSV-Datei mit C# zu lesen versuchen, die einfachste Sache ist, die Microsoft.VisualBasic.FileIO.TextFieldParser Klasse zu verwenden. Es ist tatsächlich in .NET Framework integriert und nicht als Erweiterung eines Drittanbieters.

Ja, es ist in Microsoft.VisualBasic.dll, aber das bedeutet nicht, dass Sie es nicht aus C# (oder einer anderen CLR-Sprache) verwenden können.

Hier ist ein Beispiel für die Nutzung von den MSDN documentation genommen:

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 

Auch dieses Beispiel in VB.NET ist, aber es wäre trivial es in C# zu übersetzen.

+1

+1 Ich wusste nicht über diese Klasse, aber es funktioniert wirklich gut. –

+0

Ich habe diese Klasse schon mehrmals benutzt und würde sie empfehlen. Wenn Sie in .NET integriert sind, müssen Sie sich keine Sorgen um Lizenzierungs-/Verteilungsprobleme machen. –

3

Neben dem Parsen/Lesen, einige Bibliotheken machen andere nette Dinge wie konvertieren die geparsten Daten in Objekt für Sie.

Hier ist ein Beispiel für die Verwendung CsvHelper (eine Bibliothek ich pflegen), um eine CSV-Datei in Objekte zu lesen.

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

Konventionen werden standardmäßig verwendet, um die Header/Spalten mit den Eigenschaften abzugleichen.Sie können das Verhalten ändern, indem Sie die Einstellungen ändern.

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

Manchmal muss man nicht „eigenen“ das Objekt, das Sie wollen die Daten füllen mit. In diesem Fall können Sie eine flüssige Klassenzuordnung verwenden.

// 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

Was ich nicht aus der Dokumentation verstehe ist, wann verwende ich Mapping und wann sollte ich kein Mapping verwenden? Nehmen wir an, ich möchte nur eine XML-Datei und zusätzliche Schlüsselwörter und ihre angehängten Daten analysieren. Muss ich dafür eine Karte benutzen? –

+0

Wenn Sie XML analysieren, sollten Sie keine CSV-Bibliothek verwenden. Attributzuordnung existiert seit 2.0 nicht mehr. Der Grund für die Verwendung einer Zuordnungsdatei besteht darin, dass Sie die Standardmethoden ändern möchten, mit denen die Bibliothek die Datei liest. Ein Mapping gibt Ihnen eine Menge Kontrolle darüber, wie die Datei gelesen/geschrieben wird. –

+0

Opps, das war Zettel ... ich meinte * CSV natürlich. Mapping existiert nicht in .NET 4.0/5? Was würden Sie in diesem Zusammenhang auch empfehlen, anstatt CSV zu analysieren und nur bestimmte Keywords und deren Daten zu erfassen? –

4

Ich implementiere Daniel Prydens Antwort in C#, also ist es einfacher zu schneiden und einzufügen und anzupassen. Ich denke, das ist die einfachste Methode zum Parsen von CSV-Dateien. Fügen Sie einfach eine Referenz hinzu und Sie sind im Grunde fertig.

Fügen Sie den Microsoft.VisualBasic Verweis auf Ihr Projekt

hier Dann ist Beispielcode in C# von Joel Antwort:

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

Sie Microsoft.VisualBasic.FileIO.TextFieldParser

get verwenden können unter dem Codebeispiel von oben Artikel

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