Umgang mit Kommas in einer CSV-Datei

Ich suche nach Vorschlägen, wie man mit einer CSV-Datei umgeht, die erstellt wird, die dann von unseren Kunden hochgeladen wird und die ein Komma in einem Wert, wie einem Firmennamen, haben kann.

Einige der Ideen, die wir betrachten, sind: Angegebene Bezeichner (Wert “,” Werte “,” usw.) oder mit einem | anstelle eines Kommas. Das größte Problem ist, dass wir es einfach machen müssen, oder der Kunde wird es nicht tun.

   

Wie andere bereits gesagt haben, müssen Sie Werte mit Anführungszeichen umgehen. Hier ist ein kleiner CSV-Reader in C♯, der in Anführungszeichen gesetzte Werte einschließlich eingebetteter Anführungszeichen und Zeilenumbrüche unterstützt.

Dies ist übrigens ein stückgeprüfter Code. Ich poste es jetzt, weil diese Frage viel zu kommen scheint und andere vielleicht nicht eine ganze Bibliothek wollen, wenn die einfache CSV-Unterstützung ausreicht.

Sie können es wie folgt verwenden:

using System; public class test { public static void Main() { using ( CsvReader reader = new CsvReader( "data.csv" ) ) { foreach( string[] values in reader.RowEnumerator ) { Console.WriteLine( "Row {0} has {1} values.", reader.RowIndex, values.Length ); } } Console.ReadLine(); } } 

Hier sind die classn. Beachten Sie, dass Sie mit der Csv.Escape function auch gültige CSV- Csv.Escape schreiben können.

 using System.IO; using System.Text.RegularExpressions; public sealed class CsvReader : System.IDisposable { public CsvReader( string fileName ) : this( new FileStream( fileName, FileMode.Open, FileAccess.Read ) ) { } public CsvReader( Stream stream ) { __reader = new StreamReader( stream ); } public System.Collections.IEnumerable RowEnumerator { get { if ( null == __reader ) throw new System.ApplicationException( "I can't start reading without CSV input." ); __rowno = 0; string sLine; string sNextLine; while ( null != ( sLine = __reader.ReadLine() ) ) { while ( rexRunOnLine.IsMatch( sLine ) && null != ( sNextLine = __reader.ReadLine() ) ) sLine += "\n" + sNextLine; __rowno++; string[] values = rexCsvSplitter.Split( sLine ); for ( int i = 0; i < values.Length; i++ ) values[i] = Csv.Unescape( values[i] ); yield return values; } __reader.Close(); } } public long RowIndex { get { return __rowno; } } public void Dispose() { if ( null != __reader ) __reader.Dispose(); } //============================================ private long __rowno = 0; private TextReader __reader; private static Regex rexCsvSplitter = new Regex( @",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))" ); private static Regex rexRunOnLine = new Regex( @"^[^""]*(?:""[^""]*""[^""]*)*""[^""]*$" ); } public static class Csv { public static string Escape( string s ) { if ( s.Contains( QUOTE ) ) s = s.Replace( QUOTE, ESCAPED_QUOTE ); if ( s.IndexOfAny( CHARACTERS_THAT_MUST_BE_QUOTED ) > -1 ) s = QUOTE + s + QUOTE; return s; } public static string Unescape( string s ) { if ( s.StartsWith( QUOTE ) && s.EndsWith( QUOTE ) ) { s = s.Substring( 1, s.Length - 2 ); if ( s.Contains( ESCAPED_QUOTE ) ) s = s.Replace( ESCAPED_QUOTE, QUOTE ); } return s; } private const string QUOTE = "\""; private const string ESCAPED_QUOTE = "\"\""; private static char[] CHARACTERS_THAT_MUST_BE_QUOTED = { ',', '"', '\n' }; } 

Für 2017 ist csv vollständig spezifiziert – RFC 4180.

Es ist eine sehr häufige Spezifikation und wird von vielen Bibliotheken vollständig abgedeckt ( Beispiel ).

Verwenden Sie einfach eine leicht zugängliche csv-Bibliothek – also RFC 4180.


Es gibt tatsächlich eine Spezifikation für das CSV-Format und wie man mit Kommas umgeht:

Felder, die Zeilenumbrüche (CRLF), doppelte Anführungszeichen und Kommas enthalten, sollten in doppelte Anführungszeichen gesetzt werden.

http://tools.ietf.org/html/rfc4180

Also, um Werte foo und bar,baz , tu das:

 foo,"bar,baz" 

Eine weitere wichtige Anforderung zu berücksichtigen (auch aus der Spezifikation):

Wenn doppelte Anführungszeichen verwendet werden, um Felder einzufügen, muss ein Doppelzitat, das innerhalb eines Felds erscheint, mit einem anderen doppelten Anführungszeichen versehen werden. Beispielsweise:

 "aaa","b""bb","ccc" 

Das CSV-Format verwendet Kommas zum Trennen von Werten, Werte, die Wagenrücklaufzeichen, Zeilenumbrüche, Kommas oder doppelte Anführungszeichen enthalten, sind in doppelte Anführungszeichen eingeschlossen. Werte, die doppelte Anführungszeichen enthalten, werden in Anführungszeichen gesetzt, und jedes Literal-Anführungszeichen wird durch ein unmittelbar vorangestelltes Anführungszeichen umgangen: Beispielsweise die 3 Werte:

 test list, of, items "go" he said 

würde wie folgt codiert werden:

 test "list, of, items" """go"" he said" 

Jedes Feld kann in Anführungszeichen gesetzt werden, aber nur Felder, die Kommas, CR / NL oder Anführungszeichen enthalten, müssen zitiert werden.

Es gibt keinen echten Standard für das CSV-Format, aber fast alle Anwendungen folgen den hier dokumentierten Konventionen. Der RFC, der an anderer Stelle erwähnt wurde, ist kein Standard für CSV, es ist ein RFC für die Verwendung von CSV in MIME und enthält einige unkonventionelle und unnötige Einschränkungen, die es außerhalb von MIME nutzlos machen.

Ein Problem, dass viele CSV-Module, die ich gesehen habe, nicht berücksichtigen, ist die Tatsache, dass mehrere Zeilen in einem einzigen Feld codiert werden können, was bedeutet, dass Sie nicht davon ausgehen können, dass jede Zeile ein separater Datensatz ist Daten oder bereit sein, damit umzugehen.

Setzen Sie doppelte Anführungszeichen um Strings. Das ist im Allgemeinen was Excel tut .

Ala Eli,

Sie entkommen einem doppelten Zitat als zwei doppelte Anführungszeichen. ZB “test1”, “foo” “bar”, “test2”

Sie können die Felder in Anführungszeichen setzen. Ich mag diesen Ansatz nicht, da er ein weiteres Sonderzeichen (das doppelte Zitat) hinzufügt. Definieren Sie einfach ein Escape-Zeichen (normalerweise Backslash) und verwenden Sie es, wo immer Sie etwas entkommen möchten:

  Daten, mehr Daten, mehr Daten \ sogar noch mehr 

Sie müssen nicht versuchen, Anführungszeichen zu finden, und Sie haben weniger Ausnahmen zum Parsen. Dies vereinfacht auch Ihren Code.

Es gibt eine Bibliothek, die über nugget für den Umgang mit so gut wie jeder gut gebildeten CSV (.net) – CsvHelper verfügbar ist

Beispiel für die Zuordnung zu einer class:

 var csv = new CsvReader( textReader ); var records = csv.GetRecords(); 

Beispiel zum Lesen einzelner Felder:

 var csv = new CsvReader( textReader ); while( csv.Read() ) { var intField = csv.GetField( 0 ); var stringField = csv.GetField( 1 ); var boolField = csv.GetField( "HeaderName" ); } 

Lassen Sie den Client das Dateiformat steuern:
, ist der Standardfeldbegrenzer " ist der Standardwert, der zum Ausschließen von Feldern verwendet wird, die ein Trennzeichen, ein Zitat oder eine Zeilenendung enthalten.

Um (zum Beispiel) # für Felder und ' für die Flucht zu verwenden:

 var csv = new CsvReader( textReader ); csv.Configuration.Delimiter = "#"; csv.Configuration.Quote = '''; // read the file however meets your needs 

Mehr Dokumentation

Fügen Sie einen Verweis auf die Microsoft.VisualBasic hinzu (ja, es heißt VisualBasic, aber es funktioniert auch in C # – denken Sie daran, dass am Ende alles nur IL ist).

Verwenden Sie die Microsoft.VisualBasic.FileIO.TextFieldParser class, CSV-Datei zu analysieren Hier ist der Beispielcode:

  Dim parser As TextFieldParser = New TextFieldParser("C:\mar0112.csv") parser.TextFieldType = FieldType.Delimited parser.SetDelimiters(",") While Not parser.EndOfData 'Processing row Dim fields() As String = parser.ReadFields For Each field As String In fields 'TODO: Process field Next parser.Close() End While 

Wenn Sie auf einem * nix-System sind, Zugriff auf sed und ein oder mehrere unerwünschte Kommas nur in einem bestimmten Feld Ihrer CSV vorhanden sein können, können Sie den folgenden Einzeiler verwenden, um sie in " as RFC4180 Section 2 schlägt vor:

 sed -r 's/([^,]*,[^,]*,[^,]*,)(.*)(,.*,.*)/\1"\2"\3/' inputfile 

Abhängig davon, in welchem ​​Feld sich die unerwünschten Kommas befinden, müssen Sie die Erfassungsgruppen der Regex (und die Ersetzung) ändern / erweitern.
Das obige Beispiel wird das vierte Feld (von sechs) in Anführungszeichen einschließen.

Bildbeschreibung hier eingeben

In Kombination mit der --in-place können Sie diese Änderungen direkt auf die Datei anwenden.

Um den richtigen Regex zu “bauen”, gibt es ein einfaches Prinzip:

  1. Für jedes Feld in Ihrer CSV, das vor dem Feld mit den unerwünschten Kommas steht, schreiben Sie ein [^,]*, und fügen sie alle in eine Erfassungsgruppe ein.
  2. Für das Feld, das die unerwünschten Komma (s) enthält, schreiben Sie (.*) .
  3. Für jedes Feld nach dem Feld mit dem unerwünschten Komma (s) schreibst du eins,. ,.* Und lege sie alle in eine einfangende Gruppe.

Hier ist ein kurzer Überblick über verschiedene mögliche Regexes / Substitutionen in Abhängigkeit von dem spezifischen Feld. Wenn nicht angegeben, ist die Ersetzung \1"\2"\3 .

 ([^,]*)(,.*) #first field, regex "\1"\2 #first field, substitution (.*,)([^,]*) #last field, regex \1"\2" #last field, substitution ([^,]*,)(.*)(,.*,.*,.*) #second field (out of five fields) ([^,]*,[^,]*,)(.*)(,.*) #third field (out of four fields) ([^,]*,[^,]*,[^,]*,)(.*)(,.*,.*) #fourth field (out of six fields) 

Wenn Sie die unerwünschten Kommas mit sed entfernen möchten, anstatt sie in Anführungszeichen zu setzen, lesen Sie diese Antwort .

Sie können alternative “Trennzeichen” wie “;” oder “|” aber am einfachsten könnte nur zitiert werden, was von den meisten (anständigen) CSV-Bibliotheken und den meisten anständigen Tabellen unterstützt wird.

Weitere Informationen zu CSV-Trennzeichen und eine Spezifikation für ein Standardformat zum Beschreiben von Trennzeichen und Zitaten finden Sie auf dieser Webseite

Wie in meinem Kommentar zu harpos Antwort erwähnt, ist seine Lösung gut und funktioniert in den meisten Fällen, jedoch in einigen Szenarien, wenn Kommas als direkt aneinander angrenzend nicht über die Kommas geteilt werden.

Dies liegt daran, dass sich die Regex-Zeichenfolge unerwartet als Vertabim-Zeichenfolge verhält. Um dieses Verhalten korrekt zu machen, müssen alle “Zeichen in der Regex-Zeichenfolge manuell maskiert werden, ohne das Vertabim-Escape zu verwenden.

Ie. Die Regex sollte dies mit manuellen Escapes sein:

",(?=(?:[^\"\"]*\"\"[^\"\"]*\"\")*(?![^\"\"]*\"\"))"

was übersetzt in ",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))"

Bei Verwendung einer Vertabim-Zeichenfolge @",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))" Verhält es sich wie folgend, wie Sie sehen können, wenn Sie die Regex debuggen:

 ",(?=(?:[^"]*"[^"]*")*(?![^"]*"))" 

Zusammenfassend empfehle ich die Lösung von harpo, aber passen Sie auf dieses kleine Problem auf!

Ich habe in den CsvReader ein kleines optionales Failsafe aufgenommen, um Sie zu benachrichtigen, wenn dieser Fehler auftritt (wenn Sie eine vorbekannte Anzahl von Spalten haben):

 if (_expectedDataLength > 0 && values.Length != _expectedDataLength) throw new DataLengthException(string.Format("Expected {0} columns when splitting csv, got {1}", _expectedDataLength, values.Length)); 

Dies kann über den Konstruktor eingegeben werden:

 public CsvReader(string fileName, int expectedDataLength = 0) : this(new FileStream(fileName, FileMode.Open, FileAccess.Read)) { _expectedDataLength = expectedDataLength; } 

Wenn Sie sich für eine allgemeinere Übung zum generellen Analysieren von Dateien interessieren (am Beispiel von CSV), können Sie diesen Artikel von Julian Bucknall lesen. Ich mag den Artikel, weil er die Dinge in viel kleinere Probleme aufteilt, die viel weniger unüberwindbar sind. Sie erstellen zuerst eine Grammatik und sobald Sie eine gute Grammatik haben, ist es ein relativ einfacher und methodischer process, die Grammatik in Code zu konvertieren.

Der Artikel verwendet C # und hat einen Link unten, um den Code herunterzuladen.

Wenn Sie das Rad neu erfinden möchten, könnte Folgendes für Sie funktionieren:

 public static IEnumerable SplitCSV(string line) { var s = new StringBuilder(); bool escaped = false, inQuotes = false; foreach (char c in line) { if (c == ',' && !inQuotes) { yield return s.ToString(); s.Clear(); } else if (c == '\\' && !escaped) { escaped = true; } else if (c == '"' && !escaped) { inQuotes = !inQuotes; } else { escaped = false; s.Append(c); } } yield return s.ToString(); } 

In Europa haben wir dieses Problem früher als diese Frage. In Europa verwenden wir alle ein Komma für einen Dezimalpunkt. Siehe diese Nummern unten:

 | American | Europe | | ------------- | ------------- | | 0.5 | 0,5 | | 3.14159265359 | 3,14159265359 | | 17.54 | 17,54 | | 175,186.15 | 175.186,15 | 

Daher ist es nicht möglich, das Kommatrennzeichen für CSV-Dateien zu verwenden. Aus diesem Grund sind die CSV-Dateien in Europa durch ein Semikolon ( ; ) getrennt .

Programme wie Microsoft Excel können Dateien mit einem Semikolon lesen und es ist möglich, vom Trennzeichen zu wechseln. Sie könnten sogar eine Registerkarte ( \t ) als Trennzeichen verwenden. Siehe diese Antwort von Supper User .

Da es sich um allgemeine Praktiken handelt, beginnen wir mit Faustregeln:

  1. Verwenden Sie nicht CSV, verwenden Sie XML mit einer Bibliothek, um stattdessen die XML-Datei zu lesen und zu schreiben.

  2. Wenn Sie CSV verwenden müssen. Führen Sie es ordnungsgemäß aus und verwenden Sie eine freie Bibliothek zum Analysieren und Speichern der CSV-Dateien.

Um 1) zu rechtfertigen, verschlüsseln die meisten CSV-Parser nicht bewusst, wenn Sie also nicht mit US-ASCII arbeiten, fragen Sie nach Problemen. Zum Beispiel speichert Excel 2002 die CSV in der lokalen Kodierung ohne irgendeinen Hinweis auf die Kodierung. Der CSV-Standard ist nicht weit verbreitet :(. Auf der anderen Seite ist XML-Standard gut angenommen und es verarbeitet Codierungen ziemlich gut.

Um 2) zu rechtfertigen, gibt es Tonnen von CSV-Parsern für fast alle Sprachen, so dass es nicht notwendig ist, das Rad neu zu erfinden, selbst wenn die Lösungen ziemlich einfach aussehen.

Um nur einige zu nennen:

  • für Python verwenden Sie in csv- Modul erstellen

  • für perl überprüfen CPAN und Text :: CSV

  • Für PHP verwenden Sie Build in fgetcsv / fputcsv functionen

  • für Java überprüfen SuperCVS- Bibliothek

Wirklich, es gibt keine Notwendigkeit, dies von Hand zu implementieren, wenn Sie es nicht auf dem eingebetteten Gerät analysieren werden.

Sie können die CSV-Datei so lesen.

dies nutzt Spaltungen und kümmert sich um Räume.

 ArrayList List = new ArrayList(); static ServerSocket Server; static Socket socket; static ArrayList list = new ArrayList(); public static void ReadFromXcel() throws FileNotFoundException { File f = new File("Book.csv"); Scanner in = new Scanner(f); int count =0; String[] date; String[] name; String[] Temp = new String[10]; String[] Temp2 = new String[10]; String[] numbers; ArrayList List = new ArrayList(); HashMap m = new HashMap(); in.nextLine(); date = in.nextLine().split(","); name = in.nextLine().split(","); numbers = in.nextLine().split(","); while(in.hasNext()) { String[] one = in.nextLine().split(","); List.add(one); } int xount = 0; //Making sure the lines don't start with a blank for(int y = 0; y< = date.length-1; y++) { if(!date[y].equals("")) { Temp[xount] = date[y]; Temp2[xount] = name[y]; xount++; } } date = Temp; name =Temp2; int counter = 0; while(counter < List.size()) { String[] list = List.get(counter); String sNo = list[0]; String Surname = list[1]; String Name = list[2]; for(int x = 3; x < list.length; x++) { m.put(numbers[x], list[x]); } Object newOne = new newOne(sNo, Name, Surname, m, false); StudentList.add(s); System.out.println(s.sNo); counter++; } 

Ich denke, die einfachste Lösung für dieses Problem ist, dass der Kunde den csv in Excel öffnet und dann Strg + r, um alle Komma mit dem gewünschten Bezeichner zu ersetzen. Dies ist sehr einfach für den Kunden und erfordert nur eine Änderung in Ihrem Code, um das Trennzeichen Ihrer Wahl zu lesen.

Lassen Sie uns zunächst fragen: “Warum haben wir das Gefühl, bei CSV-Dateien anders mit Kommas umzugehen?”

Für mich lautet die Antwort: “Wenn ich Daten in eine CSV-Datei exportiere, verschwinden die Kommas in einem Feld und mein Feld wird in mehrere Felder aufgeteilt, in denen die Kommas in den Originaldaten erscheinen.” (Das ist, weil das Komma das CSV-Feldtrennzeichen ist.)

Abhängig von Ihrer Situation können Semikolons auch als CSV-Feldtrennzeichen verwendet werden.

Wenn ich meine Anforderungen erfülle, kann ich ein Zeichen verwenden, z. B. ein einzelnes Niedrig-9-Anführungszeichen, das wie ein Komma aussieht.

So, hier ist, wie Sie es in Go machen können:

 // Replace special CSV characters with single low-9 quotation mark func Scrub(a interface{}) string { s := fmt.Sprint(a) s = strings.Replace(s, ",", "‚", -1) s = strings.Replace(s, ";", "‚", -1) return s } 

Das zweite Kommazeichen in der Replace-function ist Dezimal 8218.

Seien Sie sich bewusst, dass, wenn Sie Kunden haben, die ASCII-only Textleser haben, dass dieses Dezima 8218 Zeichen nicht wie ein Komma aussehen wird. Wenn dies der Fall ist, empfehle ich, das Feld mit dem Komma (oder Semikolon) mit doppelten Anführungszeichen nach RFC 4128 zu umgeben: https://tools.ietf.org/html/rfc4180

Im Allgemeinen URL-kodiere ich die Felder, die irgendwelche Kommas oder irgendwelche speziellen Zeichen haben können. Und dann decodiere es, wenn es in irgendeinem visuellen Medium verwendet / angezeigt wird.

(Kommas wird% 2C)

Jede Sprache sollte Methoden zur URL-Codierung und Decodierung von Strings haben.

zB in Java

 URLEncoder.encode(myString,"UTF-8"); //to encode URLDecoder.decode(myEncodedstring, "UTF-8"); //to decode 

Ich weiß, dass dies eine sehr allgemeine Lösung ist, und es ist möglicherweise nicht ideal für Situationen, in denen Benutzer Inhalte von CSV-Dateien manuell anzeigen möchten.

Normalerweise mache ich das in meinen CSV-Dateien Parsing-Routinen. Angenommen, die ‘line’-Variable ist eine Zeile innerhalb einer CSV-Datei und alle Werte der Spalten sind in doppelte Anführungszeichen eingeschlossen. Nachdem die beiden folgenden Zeilen ausgeführt wurden, erhalten Sie CSV-Spalten in der Sammlung “values”.

 // The below two lines will split the columns as well as trim the DBOULE QUOTES around values but NOT within them string trimmedLine = line.Trim(new char[] { '\"' }); List values = trimmedLine.Split(new string[] { "\",\"" }, StringSplitOptions.None).ToList(); 

Die einfachste Lösung, die ich gefunden habe, ist diejenige, die LibreOffice verwendet:

  1. Ersetzen Sie alle Literale durch
  2. Setzen Sie doppelte Anführungszeichen um Ihre Zeichenfolge

Sie können auch das verwenden, das Excel verwendet:

  1. Ersetze alle Literale durch ""
  2. Setzen Sie doppelte Anführungszeichen um Ihre Zeichenfolge

Beachten Sie, dass andere Personen empfohlen haben, nur Schritt 2 oben zu tun, aber das funktioniert nicht mit Zeilen, bei denen ein " gefolgt von einem, wie in einer CSV, wo Sie eine einzelne Spalte mit der Zeichenfolge hello",world haben möchten hello",world CSV würde lesen:

 "hello",world" 

Was als eine Zeile mit zwei Spalten interpretiert wird: hello und world"

  public static IEnumerable LineSplitter(this string line, char separator, char skip = '"') { var fieldStart = 0; for (var i = 0; i < line.Length; i++) { if (line[i] == separator) { yield return line.Substring(fieldStart, i - fieldStart); fieldStart = i + 1; } else if (i == line.Length - 1) { yield return line.Substring(fieldStart, i - fieldStart + 1); fieldStart = i + 1; } if (line[i] == '"') for (i++; i < line.Length && line[i] != skip; i++) { } } if (line[line.Length - 1] == separator) { yield return string.Empty; } } 

Verwenden Sie ein Tabstoppzeichen (\ t), um die Felder zu trennen.