Gestire le virgole in un file CSV

Sto cercando suggerimenti su come gestire un file CSV che viene creato, quindi caricato dai nostri clienti, e che potrebbe avere una virgola in un valore, come il nome di una società.

Alcune delle idee che stiamo osservando sono: identificatori quotati (valore “,” valori “,” ecc.) O utilizzando un | invece di una virgola. Il problema più grande è che dobbiamo renderlo facile, altrimenti il ​​cliente non lo farà.

Come altri hanno già detto, devi sfuggire ai valori che includono le virgolette. Ecco un piccolo lettore CSV in C♯ che supporta valori quotati, tra cui citazioni incorporate e ritorni a capo.

A proposito, questo è un codice testato unitamente. Lo sto postando ora perché questa domanda sembra venire molto e altri potrebbero non volere un’intera libreria quando il semplice supporto CSV lo farà.

Puoi usarlo come segue:

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

Ecco le classi. Si noti che è ansible utilizzare la funzione Csv.Escape per scrivere anche CSV validi.

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

Per il 2017, csv è pienamente specificato – RFC 4180.

È una specifica molto comune ed è completamente coperta da molte librerie ( esempio ).

Basta usare qualsiasi libreria csv facilmente disponibile – vale a dire RFC 4180.


In realtà esiste una specifica per il formato CSV e come gestire le virgole:

I campi contenenti interruzioni di riga (CRLF), doppi apici e virgole devono essere racchiusi tra virgolette.

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

Quindi, per avere valori foo e bar,baz , tu fai questo:

 foo,"bar,baz" 

Un altro requisito importante da considerare (anche dalle specifiche):

Se vengono usate virgolette per racchiudere i campi, è necessario sfuggire una virgoletta che appare all’interno di un campo precedendola con un’altra virgoletta doppia. Per esempio:

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

Il formato CSV utilizza le virgole per separare i valori, i valori che contengono ritorni a capo, linefeed, virgole o virgolette sono racchiusi tra virgolette. I valori che contengono virgolette sono quotati e ogni virgola letterale è preceduta da una citazione immediatamente precedente: Ad esempio, i 3 valori:

 test list, of, items "go" he said 

sarebbe codificato come:

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

Qualsiasi campo può essere citato ma devono essere indicati solo i campi contenenti virgole, CR / NL o citazioni.

Non esiste uno standard reale per il formato CSV, ma quasi tutte le applicazioni seguono le convenzioni documentate qui . La RFC menzionata altrove non è uno standard per CSV, è una RFC per l’utilizzo di CSV all’interno di MIME e contiene alcune limitazioni non convenzionali e inutili che lo rendono inutilizzabile al di fuori di MIME.

Un trucco che molti moduli CSV che ho visto non sono compatibili con il fatto che più righe possono essere codificate in un singolo campo, il che significa che non si può presumere che ogni riga sia un record separato, o non è necessario consentire le nuove linee nel dati o essere pronti a gestire questo.

Metti le doppie virgolette attorno alle stringhe. Questo è generalmente ciò che fa Excel .

Ala Eli,

sfuggi a una doppia citazione come due virgolette doppie. Ad esempio “test1”, “foo” “bar”, “test2”

Puoi inserire virgolette doppie nei campi. Non mi piace questo approccio, in quanto aggiunge un altro carattere speciale (la doppia citazione). Basta definire un carattere di escape (di solito backslash) e usarlo ovunque sia necessario per sfuggire a qualcosa:

  dati, più dati, più dati \, anche, ancora di più 

Non devi cercare di far corrispondere le virgolette e hai meno eccezioni da analizzare. Questo semplifica anche il tuo codice.

C’è una libreria disponibile tramite nuget per gestire praticamente qualsiasi CSV ben formato (.net) – CsvHelper

Esempio per mappare a una class:

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

Esempio per leggere i singoli campi:

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

Lasciando che il client guidi il formato del file:
, è il delimitatore di campo standard, " è il valore standard utilizzato per uscire dai campi che contengono un delimitatore, una citazione o una fine di riga.

Per utilizzare (ad esempio) # per i campi e ' per l’escape:

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

Più documentazione

Aggiungi un riferimento a Microsoft.VisualBasic (sì, dice VisualBasic ma funziona anche in C # – ricorda che alla fine è tutto solo IL).

Utilizzare la class Microsoft.VisualBasic.FileIO.TextFieldParser per analizzare il file CSV Ecco il codice di esempio:

  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 

Nel caso tu sia su un sistema * nix , abbia accesso a sed e ci possa essere una o più virgole indesiderate solo in un campo specifico del tuo CSV, puoi usare il seguente one-liner per includerli in " come RFC4180 Sezione 2 propone:

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

A seconda del campo in cui si trovano le virgole indesiderate, è necessario modificare / estendere i gruppi di cattura della regex (e della sostituzione).
L’esempio sopra racchiuderà il quarto campo (su sei) tra virgolette.

inserisci la descrizione dell'immagine qui

In combinazione con l’ --in-place puoi applicare queste modifiche direttamente al file.

Per “build” la regex corretta, c’è un semplice principio da seguire:

  1. Per ogni campo nel tuo CSV che precede il campo con le virgole indesiderate scrivi uno [^,]*, e mettili tutti insieme in un gruppo di cattura.
  2. Per il campo che contiene le virgole indesiderate che scrivi (.*) .
  3. Per ogni campo dopo il campo con le virgole indesiderate ne scrivi uno ,.* e mettili tutti insieme in un gruppo di cattura.

Ecco una breve panoramica di diverse regex / sostituzioni possibili a seconda del campo specifico. Se non specificato, la sostituzione è \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) 

Se si desidera rimuovere le virgole indesiderate con sed invece di racchiuderle tra virgolette, fare riferimento a questa risposta .

Puoi usare “delimitatori” alternativi come “;” o “|” ma la cosa più semplice potrebbe essere solo citando che è supportata dalla maggior parte delle librerie CSV (decenti) e dai fogli di calcolo più decenti.

Per ulteriori informazioni sui delimitatori CSV e una specifica per un formato standard per la descrizione dei delimitatori e delle quotazioni, consultare questa pagina Web

Come accennato nel mio commento alla risposta di harpo, la sua soluzione è buona e funziona nella maggior parte dei casi, tuttavia in alcuni scenari quando le virgole come direttamente adiacenti non riescono a dividere le virgole.

Questo perché la stringa Regex si comporta in modo imprevisto come una stringa vertabim. Per ottenere questo comportamento corretto, tutti i “caratteri nella stringa regex devono essere salvati manualmente senza utilizzare l’escape di vertabim.

Vale a dire. La regex dovrebbe essere questa utilizzando le fughe manuali:

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

che si traduce in ",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))"

Quando si utilizza una stringa vertabim @",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))" Si comporta come seguendo come puoi vedere se esegui il debug della regex:

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

Quindi, in sintesi, raccomando la soluzione di harpo, ma fai attenzione a questo piccolo trucchetto!

Ho incluso nel CsvReader un piccolo failsafe opzionale per avvisarti se si verifica questo errore (se hai un numero di colonne noto):

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

Questo può essere iniettato tramite il costruttore:

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

Se sei interessato a un esercizio più istruttivo su come analizzare i file in generale (utilizzando CSV come esempio), puoi leggere questo articolo di Julian Bucknall. Mi piace l’articolo perché rompe le cose in problemi molto più piccoli che sono molto meno insormontabili. Prima crei una grammatica e, una volta che hai una buona grammatica, è un processo relativamente facile e metodico per convertire la grammatica in codice.

L’articolo utilizza C # e ha un link in basso per scaricare il codice.

Se hai voglia di reinventare la ruota, quanto segue potrebbe funzionare per te:

 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 abbiamo questo problema prima di questa domanda. In Europa usiamo tutta una virgola per un punto decimale. Vedi questi numeri qui sotto:

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

Quindi non è ansible usare il separatore di virgola per i file CSV. Per questo motivo, i file CSV in Europa sono separati da un punto e virgola ( ; ) .

Programmi come Microsoft Excel possono leggere i file con un punto e virgola ed è ansible passare da un separatore. È anche ansible utilizzare una scheda ( \t ) come separatore. Vedi questa risposta da Supper User .

Poiché si tratta di pratiche generali, iniziamo dalle regole del pollice:

  1. Non utilizzare CSV, utilizzare XML con una libreria per leggere e scrivere il file xml.

  2. Se è necessario utilizzare CSV. Fallo correttamente e utilizza una libreria gratuita per analizzare e memorizzare i file CSV.

Per giustificare 1), la maggior parte dei parser CSV non sono codificati, quindi se non hai a che fare con US-ASCII ti stai chiedendo dei problemi. Ad esempio, excel 2002 sta memorizzando il CSV nella codifica locale senza alcuna nota sulla codifica. Lo standard CSV non è ampiamente adottato :(. D’altra parte lo standard xml è ben adottato e gestisce le codifiche abbastanza bene.

Per giustificare 2), ci sono tonnellate di parser csv in giro per quasi tutte le lingue, quindi non c’è bisogno di reinventare la ruota anche se le soluzioni sembrano piuttosto semplici.

Per citarne alcuni:

  • per python usa build nel modulo csv

  • per perl controlla CPAN e Text :: CSV

  • per php usa build nelle funzioni fgetcsv / fputcsv

  • per java controlla la libreria SuperCVS

In realtà non è necessario implementarlo manualmente se non lo si analizza sul dispositivo incorporato.

Puoi leggere il file CSV in questo modo.

questo fa uso di spaccature e si prende cura degli spazi.

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

Penso che la soluzione più semplice a questo problema sia quella di avere il cliente in grado di aprire il csv in excel, e quindi di ctrl + r per sostituire tutte le virgole con qualunque identificativo si desideri. Questo è molto facile per il cliente e richiede solo una modifica nel codice per leggere il delimitatore di tua scelta.

Per prima cosa, chiediamoci: “Perché sentiamo la necessità di gestire le virgole in modo diverso per i file CSV?”

Per me, la risposta è: “Perché quando esporto i dati in un file CSV, le virgole in un campo scompaiono e il mio campo viene separato in più campi in cui le virgole appaiono nei dati originali.” (Che perché la virgola è il carattere separatore di campo CSV.)

A seconda della situazione, i semi-colon possono anche essere utilizzati come separatori di campi CSV.

Date le mie esigenze, posso usare un carattere, ad esempio, un singolo virgolette low-9, che assomiglia a una virgola.

Quindi, ecco come puoi farlo in Go:

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

Il secondo simbolo di virgola nella funzione Sostituisci è decimale 8218.

Si noti che se si dispone di client che potrebbero avere lettori di testo ascii-only, questo carattere decima 8218 non assomiglierà a una virgola. Se questo è il tuo caso, ti consiglio di circondare il campo con la virgola (o punto e virgola) con virgolette per RFC 4128: https://tools.ietf.org/html/rfc4180

In genere, codifico per URL i campi che possono contenere virgole o caratteri speciali. E poi decodificarlo quando viene usato / visualizzato in qualsiasi supporto visivo.

(le virgole diventano% 2C)

Ogni lingua dovrebbe avere metodi per codificare e decodificare le stringhe.

ad es. in java

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

So che questa è una soluzione molto generale e potrebbe non essere l’ideale per le situazioni in cui l’utente desidera visualizzare il contenuto del file csv, manualmente.

Solitamente lo faccio nei miei file CSV durante l’analisi delle routine. Supponiamo che la variabile ‘line’ sia una riga all’interno di un file CSV e tutti i valori delle colonne siano racchiusi tra virgolette. Dopo l’esecuzione delle due righe sottostanti, otterrai colonne CSV nella raccolta “valori”.

 // 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(); 

La soluzione più semplice che ho trovato è quella che LibreOffice utilizza:

  1. Sostituisci tutto letterale " per
  2. Metti le virgolette doppie attorno alla tua corda

Puoi anche usare quello che usa Excel:

  1. Sostituisci tutto letterale " per ""
  2. Metti le virgolette doppie attorno alla tua corda

Si noti che altre persone consigliano di fare solo il punto 2 sopra, ma che non funziona con le righe in cui " è seguito da un, come in un CSV in cui si desidera avere una singola colonna con la stringa hello",world , come CSV avrebbe letto:

 "hello",world" 

Che è interpretato come una riga con due colonne: hello e 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; } } 

Utilizzare un carattere di tabulazione (\ t) per separare i campi.