2013-04-25 13 views
10

Sto tentando di importare un file CSV utilizzando TextFieldParser. Un particolare file CSV mi sta causando problemi a causa della sua formattazione non standard. Il CSV in questione ha i suoi campi racchiusi tra virgolette. Il problema si presenta quando c'è un ulteriore set di virgolette doppie senza escape in un particolare campo.Gestione dei campi contenenti doppie virgolette senza escape con TextFieldParser

Ecco un caso di test troppo semplificato che evidenzia il problema. I file CSV effettivi con cui ho a che fare non sono tutti formattati allo stesso modo e hanno dozzine di campi, ognuno dei quali può contenere questi problemi di formattazione probabilmente complicati.

TextReader reader = new StringReader("\"Row\",\"Test String\"\n" + 
    "\"1\",\"This is a test string. It is parsed correctly.\"\n" + 
    "\"2\",\"This is a test string with a comma, which is parsed correctly\"\n" + 
    "\"3\",\"This is a test string with double \"\"double quotes\"\". It is parsed correctly\"\n" + 
    "\"4\",\"This is a test string with 'single quotes'. It is parsed correctly\"\n" + 
    "5,This is a test string with fields that aren't enclosed in double quotes. It is parsed correctly.\n" + 
    "\"6\",\"This is a test string with single \"double quotes\". It can't be parsed.\""); 

using (TextFieldParser parser = new TextFieldParser(reader)) 
{ 
    parser.Delimiters = new[] { "," }; 
    while (!parser.EndOfData) 
    { 
     string[] fields= parser.ReadFields(); 
     Console.WriteLine("This line was parsed as:\n{0},{1}", 
      fields[0], fields[1]); 
    } 
} 

È comunque necessario analizzare correttamente un CSV con questo tipo di formattazione utilizzando TextFieldParser?

+5

È molto importante non provare a risolverlo. Questo ti renderà responsabile per i cattivi dati per molto tempo. Rifiuta il file per essere formattato in modo errato. Se ti mettono in imbarazzo, fai notare che non è compatibile con RFC-4180. C'è un altro programmatore da qualche parte che può facilmente risolvere questo problema. –

+2

@HansPassant Mentre questa è la linea di condotta ideale e "corretta", molte volte non abbiamo una scelta, ad esempio quando consumiamo file da un'API su cui non abbiamo alcun controllo, o un cliente che è importante e di cui abbiamo bisogno semplicemente "farlo funzionare". – richard

risposta

5

Sono d'accordo con il consiglio di Hans passant che non è vostra responsabilità di analizzare i dati malformati. Tuttavia, in accordo con lo Robustness Principle, qualcuno di fronte a questa situazione potrebbe tentare di gestire tipi specifici di dati non validi. Il codice che ho scritto di seguito funziona sul set di dati specificato nella domanda. Fondamentalmente rileva l'errore del parser sulla linea malformata, determina se è racchiuso in doppia citazione in base al primo carattere e quindi divide/rimuove manualmente tutte le virgolette di wrapping.

using (TextFieldParser parser = new TextFieldParser(reader)) 
{ 
    parser.Delimiters = new[] { "," }; 

    while (!parser.EndOfData) 
    { 
     string[] fields = null; 
     try 
     { 
      fields = parser.ReadFields(); 
     } 
     catch (MalformedLineException ex) 
     { 
      if (parser.ErrorLine.StartsWith("\"")) 
      { 
       var line = parser.ErrorLine.Substring(1, parser.ErrorLine.Length - 2); 
       fields = line.Split(new string[] { "\",\"" }, StringSplitOptions.None); 
      } 
      else 
      { 
       throw; 
      } 
     } 
     Console.WriteLine("This line was parsed as:\n{0},{1}", fields[0], fields[1]); 
    } 
} 

Sono sicuro che è possibile inventare un esempio patologica in cui questo non riesce (ad esempio le virgole adiacenti virgolette all'interno di un valore di campo), ma tali esempi sarebbe probabilmente di analizzarlo in senso stretto, mentre la la linea di problema data nella domanda è decifrabile nonostante sia malformata.

+2

Considerando che sono passati quasi due anni da quando ho postato questa domanda, non sono sicuro che questo avrebbe risolto il mio problema iniziale. Ho finito con il suggerimento di Hans e ho chiesto un file più vicino alle specifiche. Poiché questo risolve i miei casi esemplificativi e non ho mai accettato una risposta, andrò avanti e accetterò la tua risposta. Grazie, mi hai salvato da diventare un altro DenverCoder9 - https://xkcd.com/979/ – sglantz

+2

Ah, l'obbligatorio XKCD :) Sì, sapevo che stavo aprendo una vecchia domanda, ma avevo un problema molto simile al tuo, e quando ho trovato una soluzione ho pensato che sarebbe stato bello condividerla. –

0

Può essere più facile di fare proprio questo manualmente, e sarebbe sicuramente vi darà un maggiore controllo:

Edit: Per il vostro esempio chiarito, io ancora suggerisco di movimentazione manuale il parsing:

using System.IO; 

string[] csvFile = File.ReadAllLines(pathToCsv); 
foreach (string line in csvFile) 
{ 
    // get the first comma in the line 
    // everything before this index is the row number 
    // everything after is the row value 
    int firstCommaIndex = line.IndexOf(','); 

    //Note: SubString used here is (startIndex, length) 
    string row = line.Substring(0, firstCommaIndex+1); 
    string rowValue = line.Substring(firstCommaIndex+1).Trim(); 

    Console.WriteLine("This line was parsed as:\n{0},{1}", 
      row, rowValue); 
} 

per un CSV generica che non consente le virgole nei campi:

using System.IO; 

string[] csvFile = File.ReadAllLines(pathToCsv); 
foreach (string line in csvFile) 
{ 
    string[] fields = line.Split(','); 
    Console.WriteLine("This line was parsed as:\n{0},{1}", 
      fields[0], fields[1]); 
} 
+0

Sembra che avrei potuto semplificare il mio esempio al punto che non è chiaro il motivo per cui avrei usato TextFieldParser. Una semplice divisione su una virgola finirà per introdurre una varietà di problemi diversi che non sono presenti quando si utilizza TextFieldParser. L'esempio principale è l'esistenza di una virgola all'interno di un valore di testo specifico. Aggiornerò la domanda con una stringa di test più complessa per evidenziare i vantaggi dell'utilizzo di TextFieldParser. – sglantz

+0

@sglantz: campione di codice aggiornato. Sto ancora suggerendo che otterrete il massimo controllo facendo questo manualmente. Ho trovato il problema con CSV e altri parser è che è facile avere dati che non funzionano con loro anche quando sono programmati per essere piuttosto generici. Penso che il nuovo esempio di codice sia più facile da leggere, ma può essere realizzato anche usando le espressioni regolari C# e la classe Match. –

+0

Sembra ancora che l'esempio non trasmetta ancora la complessità dei file CSV con cui ho a che fare. Non sono formattati in modo coerente. Virgole e virgolette possono apparire in uno qualsiasi dei 20 campi del file. TextFieldParser è in grado di gestire tale incoerenza mentre si divide manualmente e anche la regex diventa molto complessa molto rapidamente quando si ha a che fare con una varietà di formati diversi. – sglantz

-1

Si prega di impostare HasFieldsEnclosed InQuotes = true sull'oggetto TextFieldParser prima di iniziare a leggere il file.

+1

Questa opzione può essere attivata e TextParser non sarà ancora in grado di analizzare il testo. Il problema non è che i campi siano racchiusi tra virgolette, ma all'interno dei campi ci sono virgolette che non vengono scappate correttamente usando due virgolette. – sglantz

0

di lavoro Soluzione:

using (TextFieldParser csvReader = new TextFieldParser(csv_file_path)) 
      { 
       csvReader.SetDelimiters(new string[] { "," }); 
       csvReader.HasFieldsEnclosedInQuotes = false; 
       string[] colFields = csvReader.ReadFields(); 

       while (!csvReader.EndOfData) 
       { 
        string[] fieldData = csvReader.ReadFields(); 
        for (i = 0; i < fieldData.Length; i++) 
        { 
         if (fieldData[i] == "") 
         { 
          fieldData[i] = null; 
         } 
         else 
         { 
          if (fieldData[i][0] == '"' && fieldData[i][fieldData[i].Length - 1] == '"') 
          { 
           fieldData[i] = fieldData[i].Substring(1, fieldData[i].Length - 2); 
          } 
         } 
        } 
        csvData.Rows.Add(fieldData); 
        } 
      } 
+0

Questo non funzionerà se ci sono virgole nei valori del campo. –

+0

Per chiarire, le virgole nella stringa di test n. 2 causano la divisione dei campi errata. –

0

Se non si imposta HasFieldsEnclosedInQuotes = true l'elenco risultante di colonne sarà maggiore se i dati contengono (,) virgola. "Col1", "Col2", "Col3" "Test1", 100, "Test1, Test2" "Test2", 200, "Test22" Questo file deve avere 3 colonne ma durante l'analisi si otterrà 4 campi che è sbagliato.

Problemi correlati