CSV-Zeichenfolgenbehandlung

  • Typische Methode zum Erstellen einer CSV -Spezifikation (Pseudocode) ):

    1. Erstellen Sie ein CSV-Containerobjekt (wie ein StringBuilder in C #).
    2. Durchlaufen Sie die gewünschten Zeichenfolgen Hinzufügen eines Anhängers nach jedem Komma.
    3. Entfernen Sie nach der Schleife das letzte überflüssige Komma.

    Code Beispiel:

     public string ReturnAsCSV(ContactList contactList)
    {
        StringBuilder sb = new StringBuilder();
        foreach (Contact c in contactList)
        {
            sb.Append(c.Name + ",");
        }
    
        sb.Remove(sb.Length - 1, 1);
        //sb.Replace(",", "", sb.Length - 1, 1)
    
        return sb.ToString();
    }
     

    Ich mag die Idee, das Komma hinzuzufügen, indem ich prüfe, ob der Container leer ist, aber nicht ' Das bedeutet mehr Verarbeitung, da die Länge der Zeichenfolge bei jedem Vorkommen überprüft werden muss.

    Ich denke, dass es eine einfachere / sauberere / effizientere Methode geben sollte, um das letzte Mal zu entfernen Komma. Irgendwelche Ideen?

    07 February 2016
    Markus SafarMHop
13 answers
  • Sie können LINQ to Objects verwenden:

     string [] strings = contactList.Select(c => c.Name).ToArray();
    string csv = string.Join(",", strings);
     

    Natürlich könnte das alles in einer Zeile erfolgen, aber bei zwei ist es etwas klarer.

    26 March 2010
    Matthew Grovesczk
  • Ihr Code entspricht nicht wirklich dem vollem CSV-Format . Wenn Sie CSV nur aus Daten ohne Kommas, Leerzeichen, Tabulatoren, Zeilenumbrüche oder Anführungszeichen generieren, sollte dies in Ordnung sein. In den meisten Datenaustausch-Szenarien in der Praxis benötigen Sie jedoch die vollständige Implementierung.

    Für die Generierung zur richtigen CSV können Sie Folgendes verwenden:

     public static String EncodeCsvLine(params String[] fields)
    {
        StringBuilder line = new StringBuilder();
    
        for (int i = 0; i < fields.Length; i++)
        {
            if (i > 0)
            {
                line.Append(DelimiterChar);
            }
    
            String csvField = EncodeCsvField(fields[i]);
            line.Append(csvField);
        }
    
        return line.ToString();
    }
    
    static String EncodeCsvField(String field)
    {
        StringBuilder sb = new StringBuilder();
        sb.Append(field);
    
        // Some fields with special characters must be embedded in double quotes
        bool embedInQuotes = false;
    
        // Embed in quotes to preserve leading/tralining whitespace
        if (sb.Length > 0 && 
            (sb[0] == ' ' || 
             sb[0] == '\t' ||
             sb[sb.Length-1] == ' ' || 
             sb[sb.Length-1] == '\t' ))
        {
            embedInQuotes = true;
        }
    
        for (int i = 0; i < sb.Length; i++)
        {
            // Embed in quotes to preserve: commas, line-breaks etc.
            if (sb[i] == DelimiterChar || 
                sb[i]=='\r' || 
                sb[i]=='\n' || 
                sb[i] == '"') 
            { 
                embedInQuotes = true;
                break;
            }
        }
    
        // If the field itself has quotes, they must each be represented 
        // by a pair of consecutive quotes.
        sb.Replace("\"", "\"\"");
    
        String rv = sb.ToString();
    
        if (embedInQuotes)
        {
            rv = "\"" + rv + "\"";
        }
    
        return rv;
    }
     

    Vielleicht nicht der effizienteste Code der Welt, aber er wurde getestet. Im Vergleich zum schnellen Beispielcode ist die reale Welt scheiße:)

    07 February 2016
    Markus SafarMHop
  • Vergessen Sie nicht unseren alten Freund "für". Es sieht nicht so gut aus wie Foreach, hat aber den Vorteil, dass man beim zweiten Element beginnen kann.

     public string ReturnAsCSV(ContactList contactList)
    {
        if (contactList == null || contactList.Count == 0)
            return string.Empty;
    
        StringBuilder sb = new StringBuilder(contactList[0].Name);
    
        for (int i = 1; i < contactList.Count; i++)
        {
            sb.Append(",");
            sb.Append(contactList[i].Name);
        }
    
        return sb.ToString();
    }
     

    Sie können den zweiten Append auch in ein "if" einfassen, das prüft, ob die Name-Eigenschaft ein doppeltes Anführungszeichen oder ein Komma enthält, und wenn dies der Fall ist, schützen Sie sie entsprechend

    07 February 2016
    Markus SafarMHop
  • Warum verwenden Sie nicht eine der Open-Source-CSV-Bibliotheken da draußen?

    Ich weiß, dass es sich nach etwas anhört, das so aussieht einfach, aber wie man an den Kommentaren und Codeausschnitten erkennen kann, gibt es mehr als man auf den ersten Blick erkennt. Abgesehen von der Einhaltung der vollständigen CSV-Konformität möchten Sie eventuell auch mit dem Lesen und Schreiben von CSVs umgehen ... und möglicherweise auch die Dateibearbeitung.

    Ich habe CSV öffnen für eines meiner Projekte zuvor (aber es gibt viele andere zur Auswahl). Es hat mir sicherlich das Leben leichter gemacht. ;)

    20 August 2008
    yoliho
  • Sie können stattdessen das Komma als erstes in Ihrem Foreach hinzufügen.

    if (sb.Length > 0) sb.Append(",");

    26 November 2011
    Dorababu Meka
  • Sie können auch ein Array aus c.Name -Daten erstellen und die String.Join -Methode verwenden, um Ihre Linie zu erstellen.

     public string ReturnAsCSV(ContactList contactList)
    {
        List<String> tmpList = new List<string>();
    
        foreach (Contact c in contactList)
        {
            tmpList.Add(c.Name);
        }
    
        return String.Join(",", tmpList.ToArray());
    }
     

    Dies ist möglicherweise nicht so leistungsfähig wie der StringBuilder -Ansatz, aber es sieht definitiv sauberer aus |>

    Möglicherweise möchten Sie auch .CurrentCulture.TextInfo.ListSeparator anstelle eines hartcodierten Kommas verwenden. - Wenn Ihre Ausgabe in andere Anwendungen importiert wird, Sie könnten Probleme damit haben. ListSeparator kann sich in verschiedenen Kulturen unterscheiden, und MS Excel berücksichtigt zumindest diese Einstellung. Also:

     return String.Join(
        System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator,
        tmpList.ToArray());
     
    07 February 2016
    Markus SafarMHop
  • Ich mag die Idee, das Komma hinzuzufügen, indem ich prüfe, ob der Container leer ist, aber bedeutet das nicht mehr Verarbeitung, da die Länge der Zeichenfolge bei jedem Vorkommen überprüft werden muss?

    Sie optimieren vorzeitig, der Performance-Treffer wäre vernachlässigbar.

    07 August 2008
    Xenph Yan
  • Ich habe eine kleine Klasse dafür geschrieben, falls jemand anderes es für nützlich hält ...

     public class clsCSVBuilder
    {
        protected int _CurrentIndex = -1;
        protected List<string> _Headers = new List<string>();
        protected List<List<string>> _Records = new List<List<string>>();
        protected const string SEPERATOR = ",";
    
        public clsCSVBuilder() { }
    
        public void CreateRow()
        {
            _Records.Add(new List<string>());
            _CurrentIndex++;
        }
    
        protected string _EscapeString(string str)
        {
            return string.Format("\"{0}\"", str.Replace("\"", "\"\"")
                                                .Replace("\r\n", " ")
                                                .Replace("\n", " ")
                                                .Replace("\r", " "));
        }
    
        protected void _AddRawString(string item)
        {
            _Records[_CurrentIndex].Add(item);
        }
    
        public void AddHeader(string name)
        {
            _Headers.Add(_EscapeString(name));
        }
    
        public void AddRowItem(string item)
        {
            _AddRawString(_EscapeString(item));
        }
    
        public void AddRowItem(int item)
        {
            _AddRawString(item.ToString());
        }
    
        public void AddRowItem(double item)
        {
            _AddRawString(item.ToString());
        }
    
        public void AddRowItem(DateTime date)
        {
            AddRowItem(date.ToShortDateString());
        }
    
        public static string GenerateTempCSVPath()
        {
            return Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString().ToLower().Replace("-", "") + ".csv");
        }
    
        protected string _GenerateCSV()
        {
            StringBuilder sb = new StringBuilder();
    
            if (_Headers.Count > 0)
            {
                sb.AppendLine(string.Join(SEPERATOR, _Headers.ToArray()));
            }
    
            foreach (List<string> row in _Records)
            {
                sb.AppendLine(string.Join(SEPERATOR, row.ToArray()));
            }
    
            return sb.ToString();
        }
    
        public void SaveAs(string path)
        {
            using (StreamWriter sw = new StreamWriter(path))
            {
                sw.Write(_GenerateCSV());
            }
        }
    }
     
    26 June 2012
    jocull
  • Ich habe diese Methode bereits verwendet. Die Length-Eigenschaft von StringBuilder ist NICHT schreibgeschützt. Wenn Sie sie also um eins abziehen, wird das letzte Zeichen abgeschnitten. Sie müssen jedoch sicherstellen, dass Ihre Länge nicht gleich Null ist (was passieren würde, wenn Ihre Liste leer ist), da das Festlegen der Länge unter Null Null ist.

     [pre> public string ReturnAsCSV(ContactList contactList)
    {
        StringBuilder sb = new StringBuilder();
    
        foreach (Contact c in contactList)       
        { 
            sb.Append(c.Name + ",");       
        }
    
        if (sb.Length > 0)  
            sb.Length -= 1;
    
        return sb.ToString();  
    }
     
    07 February 2016
    Markus SafarMHop
  • Nur ein Gedanke, aber denken Sie daran, mit Kommas und Anführungszeichen (") in den Feldwerten zu arbeiten, andernfalls kann Ihre CSV-Datei den Leser der Verbraucher beschädigen.

    07 August 2008
    KevBrian