Come dividere CSV le cui colonne possono contenere,


105

Dato

2,1016,7 / 31/2008 14:22, Geoff Dalgas , 6/5/2011 22:21, http://stackoverflow.com , "Corvallis, OR", 7679,351,81, b437f461b3fd27387c5d8ab47a293d35,34

Come utilizzare C # per suddividere le informazioni di cui sopra in stringhe come segue:

2
1016
7/31/2008 14:22
Geoff Dalgas
6/5/2011 22:21
http://stackoverflow.com
Corvallis, OR
7679
351
81
b437f461b3fd27387c5d8ab47a293d35
34

Come puoi vedere una delle colonne contiene, <= (Corvallis, OR)

// update // Basato su C # Regex Split - virgole fuori virgolette

string[] result = Regex.Split(samplestring, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");

1
Anche se in Java, simile Domanda: stackoverflow.com/questions/1757065/...
sgokhales

1
Usare una regex per farlo è un cattivo consiglio. .NET Framework ha già il supporto integrato per analizzare CSV. Vedi questa risposta che è quella che dovresti accettare. Altrimenti lo chiuderò come un duplicato di stackoverflow.com/questions/3147836/… che è altrettanto sbagliato.
Kev

Puoi spiegarci qual è il supporto integrato di .NET per l'analisi dei file CSV con virgole incorporate? Ti riferisci alla classe Microsoft.VisualBasic.FileIO.TextFieldParser?
AllSolutions

Risposte:


182

Usa la Microsoft.VisualBasic.FileIO.TextFieldParserclasse. Questo gestirà l'analisi di un file delimitato TextReadero Streamdove alcuni campi sono racchiusi tra virgolette e altri no.

Per esempio:

using Microsoft.VisualBasic.FileIO;

string csv = "2,1016,7/31/2008 14:22,Geoff Dalgas,6/5/2011 22:21,http://stackoverflow.com,\"Corvallis, OR\",7679,351,81,b437f461b3fd27387c5d8ab47a293d35,34";

TextFieldParser parser = new TextFieldParser(new StringReader(csv));

// You can also read from a file
// TextFieldParser parser = new TextFieldParser("mycsvfile.csv");

parser.HasFieldsEnclosedInQuotes = true;
parser.SetDelimiters(",");

string[] fields;

while (!parser.EndOfData)
{
    fields = parser.ReadFields();
    foreach (string field in fields)
    {
        Console.WriteLine(field);
    }
} 

parser.Close();

Ciò dovrebbe comportare il seguente output:

2
1016
7/31/2008 14:22
Geoff Dalgas
6/5/2011 22:21
http://stackoverflow.com
Corvallis, OR
7679
351
81
b437f461b3fd27387c5d8ab47a293d35
34

Per ulteriori informazioni, vedere Microsoft.VisualBasic.FileIO.TextFieldParser .

È necessario aggiungere un riferimento a Microsoft.VisualBasicnella scheda Aggiungi riferimenti .NET.


9
Amico, grazie mille per questa soluzione, ho circa 500K + righe di dati CSV che devo caricare in una tabella e sono state caricate con virgole contenute tra virgolette. Ti devo una bevanda adulta di tua scelta se le nostre strade si incrociassero.
Mark Kram

@tim l'ho usato e nota che salta tutti i numeri di riga pari, elaborando solo i numeri di riga dispari in un file che ha 1050 righe. qualche idea?
Smith

@ Smith - Senza vedere il codice o l'input di esempio non ne ho idea. Suggerisco di pubblicare una nuova domanda. Forse nel file manca un ritorno a capo o un altro indicatore di fine riga sulle linee pari?
Tim

Non sapevo nemmeno di questa libreria fino a quando non l'ho vista - grazie! Se qualcun altro desidera un esempio che analizzi un intero file CSV, vedere questa risposta SO: stackoverflow.com/a/3508572/3105807
Amy Barrett

2
Possiamo linciare Microsoft per non aver fornito un costruttore che accetta una stringa, quindi dobbiamo prima passare attraverso il cerchio della conversione in un flusso? Altrimenti, bella risposta.
Loren Pechtel

43

È così tardi ma questo può essere utile per qualcuno. Possiamo usare RegEx come muggito.

Regex CSVParser = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");
String[] Fields = CSVParser.Split(Test);

4
Questo è perfetto. Preferisco usarlo piuttosto che importare un'intera altra libreria. Bravo.
TheGeekYouNeed

1
Corrisponde a asdf, "", "come ,\" df ",

Questa soluzione non funziona correttamente: non tiene conto dei segni di discorso, il che significa che ci saranno molti segni di discorso in posizioni errate durante la lettura.
AidanH

E se le virgolette finali mancassero in una riga: asd, "", "as, \" df "," asd asd "," as
MarmiK

1
Questo ha funzionato per me e ha rappresentato i segni di discorso tra virgolette. 30 milioni di file. Molto buono e una quantità minima di codice.
GBGOLC


4

Vedo che se incolli del testo delimitato da CSV in Excel e fai un "Testo in colonne", ti viene chiesto un "qualificatore di testo". L'impostazione predefinita è una virgoletta doppia in modo che tratti il ​​testo tra virgolette doppie come letterale. Immagino che Excel lo implementa andando un carattere alla volta, se incontra un "qualificatore di testo", continua ad andare al "qualificatore" successivo. Probabilmente puoi implementarlo tu stesso con un ciclo for e un booleano per denotare se sei all'interno di un testo letterale.

public string[] CsvParser(string csvText)
{
    List<string> tokens = new List<string>();

    int last = -1;
    int current = 0;
    bool inText = false;

    while(current < csvText.Length)
    {
        switch(csvText[current])
        {
            case '"':
                inText = !inText; break;
            case ',':
                if (!inText) 
                {
                    tokens.Add(csvText.Substring(last + 1, (current - last)).Trim(' ', ',')); 
                    last = current;
                }
                break;
            default:
                break;
        }
        current++;
    }

    if (last != csvText.Length - 1) 
    {
        tokens.Add(csvText.Substring(last+1).Trim());
    }

    return tokens.ToArray();
}

3

Usa una libreria come LumenWorks per leggere i file CSV. Gestirà i campi con virgolette e sarà probabilmente nel complesso più robusto della tua soluzione personalizzata in virtù del fatto che esiste da molto tempo.


2

È difficile analizzare i file .csv quando il file .csv può essere costituito da stringhe separate da virgole, stringhe separate da virgole o da una combinazione caotica dei due. La soluzione che ho trovato consente una qualsiasi delle tre possibilità.

Ho creato un metodo, ParseCsvRow () che restituisce un array da una stringa csv. Per prima cosa mi occupo delle virgolette doppie nella stringa suddividendo la stringa tra virgolette doppie in un array chiamato quotesArray. I file .csv della stringa tra virgolette sono validi solo se è presente un numero pari di virgolette doppie. Le virgolette doppie nel valore di una colonna devono essere sostituite con una coppia di virgolette doppie (questo è l'approccio di Excel). Finché il file .csv soddisfa questi requisiti, puoi aspettarti che le virgole delimitatore vengano visualizzate solo al di fuori delle coppie di virgolette doppie. Le virgole all'interno delle coppie di virgolette doppie fanno parte del valore della colonna e devono essere ignorate quando si divide il .csv in un array.

Il mio metodo verificherà le virgole al di fuori delle coppie di virgolette doppie guardando solo gli indici pari di quotesArray. Rimuove anche le virgolette doppie all'inizio e alla fine dei valori di colonna.

    public static string[] ParseCsvRow(string csvrow)
    {
        const string obscureCharacter = "ᖳ";
        if (csvrow.Contains(obscureCharacter)) throw new Exception("Error: csv row may not contain the " + obscureCharacter + " character");

        var unicodeSeparatedString = "";

        var quotesArray = csvrow.Split('"');  // Split string on double quote character
        if (quotesArray.Length > 1)
        {
            for (var i = 0; i < quotesArray.Length; i++)
            {
                // CSV must use double quotes to represent a quote inside a quoted cell
                // Quotes must be paired up
                // Test if a comma lays outside a pair of quotes.  If so, replace the comma with an obscure unicode character
                if (Math.Round(Math.Round((decimal) i/2)*2) == i)
                {
                    var s = quotesArray[i].Trim();
                    switch (s)
                    {
                        case ",":
                            quotesArray[i] = obscureCharacter;  // Change quoted comma seperated string to quoted "obscure character" seperated string
                            break;
                    }
                }
                // Build string and Replace quotes where quotes were expected.
                unicodeSeparatedString += (i > 0 ? "\"" : "") + quotesArray[i].Trim();
            }
        }
        else
        {
            // String does not have any pairs of double quotes.  It should be safe to just replace the commas with the obscure character
            unicodeSeparatedString = csvrow.Replace(",", obscureCharacter);
        }

        var csvRowArray = unicodeSeparatedString.Split(obscureCharacter[0]); 

        for (var i = 0; i < csvRowArray.Length; i++)
        {
            var s = csvRowArray[i].Trim();
            if (s.StartsWith("\"") && s.EndsWith("\""))
            {
                csvRowArray[i] = s.Length > 2 ? s.Substring(1, s.Length - 2) : "";  // Remove start and end quotes.
            }
        }

        return csvRowArray;
    }

Uno svantaggio del mio approccio è il modo in cui sostituisco temporaneamente le virgole delimitatrici con un oscuro carattere Unicode. Questo personaggio deve essere così oscuro, non verrebbe mai visualizzato nel tuo file .csv. Potresti voler dare più maneggevolezza a questo.


1

Ho avuto un problema con un CSV che contiene campi con un carattere di virgolette, quindi utilizzando TextFieldParser, ho ottenuto quanto segue:

private static string[] parseCSVLine(string csvLine)
{
  using (TextFieldParser TFP = new TextFieldParser(new MemoryStream(Encoding.UTF8.GetBytes(csvLine))))
  {
    TFP.HasFieldsEnclosedInQuotes = true;
    TFP.SetDelimiters(",");

    try 
    {           
      return TFP.ReadFields();
    }
    catch (MalformedLineException)
    {
      StringBuilder m_sbLine = new StringBuilder();

      for (int i = 0; i < TFP.ErrorLine.Length; i++)
      {
        if (i > 0 && TFP.ErrorLine[i]== '"' &&(TFP.ErrorLine[i + 1] != ',' && TFP.ErrorLine[i - 1] != ','))
          m_sbLine.Append("\"\"");
        else
          m_sbLine.Append(TFP.ErrorLine[i]);
      }

      return parseCSVLine(m_sbLine.ToString());
    }
  }
}

Uno StreamReader è ancora utilizzato per leggere il CSV riga per riga, come segue:

using(StreamReader SR = new StreamReader(FileName))
{
  while (SR.Peek() >-1)
    myStringArray = parseCSVLine(SR.ReadLine());
}

1

Con Cinchoo ETL , una libreria open source, può gestire automaticamente i valori delle colonne contenenti separatori.

string csv = @"2,1016,7/31/2008 14:22,Geoff Dalgas,6/5/2011 22:21,http://stackoverflow.com,""Corvallis, OR"",7679,351,81,b437f461b3fd27387c5d8ab47a293d35,34";

using (var p = ChoCSVReader.LoadText(csv)
    )
{
    Console.WriteLine(p.Dump());
}

Produzione:

Key: Column1 [Type: String]
Value: 2
Key: Column2 [Type: String]
Value: 1016
Key: Column3 [Type: String]
Value: 7/31/2008 14:22
Key: Column4 [Type: String]
Value: Geoff Dalgas
Key: Column5 [Type: String]
Value: 6/5/2011 22:21
Key: Column6 [Type: String]
Value: http://stackoverflow.com
Key: Column7 [Type: String]
Value: Corvallis, OR
Key: Column8 [Type: String]
Value: 7679
Key: Column9 [Type: String]
Value: 351
Key: Column10 [Type: String]
Value: 81
Key: Column11 [Type: String]
Value: b437f461b3fd27387c5d8ab47a293d35
Key: Column12 [Type: String]
Value: 34

Per ulteriori informazioni, visita l'articolo di codeproject.

Spero che sia d'aiuto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.