Parse délimité CSV in.NET

j'ai un fichier texte qui est dans un format séparé par des virgules, délimité par " sur la plupart des champs. J'essaie de mettre cela dans quelque chose que je peux énumérer à travers (Collection générique, par exemple). Je n'ai pas le contrôle sur la façon dont le fichier est sorti ni le caractère qu'il utilise pour le délimiteur.

Dans ce cas, les champs sont séparés par une virgule et les champs de texte sont inclus dans " les marques. Le problème que je rencontre est que certains champs ont des guillemets (i.e. 8" Tray) et sont accidentellement repris comme champ suivant. Dans le cas de champs numériques, ils n'ont pas de guillemets autour d'eux, mais ils ne commencent avec un + ou un - signe (représentant un positif/négatif).

je pensais à un RegEx, mais mes compétences ne sont pas si grandes que ça, donc j'espère que quelqu'un pourra trouver des idées que je peux essayer. Il y a environ 19 000 enregistrements dans ce fichier, donc j'essaie de le faire aussi efficacement que possible. Voici quelques exemples de lignes de données:

"00","000000112260   ","Pie Pumpkin                             ","RET","6.99 ","     ","ea ",+0000000006.99000
"00","000000304078   ","Pie Apple caramel                       ","RET","9.99 ","     ","ea ",+0000000009.99000
"00","StringValue here","8" Tray of Food                             ","RET","6.99 ","     ","ea ",-00000000005.3200

Il y a beaucoup plus de champs, mais vous pouvez obtenir de l'image....

j'utilise VB.NET et j'ai une liste générique pour accepter les données. J'ai essayé d'utiliser CSVReader et il semble bien fonctionner jusqu'à ce que vous atteindre un record comme le 3ème (avec une citation dans le champ de texte). Si je pouvais d'une façon ou d'une autre l'obtenir pour gérer les citations supplémentaires, que L'option CSVReader fonctionnera très bien.

Merci!

23
demandé sur Danny_ds 2009-04-10 07:22:17

12 réponses

ici:

Encoding fileEncoding = GetFileEncoding(csvFile);
// get rid of all doublequotes except those used as field delimiters
string fileContents = File.ReadAllText(csvFile, fileEncoding);
string fixedContents = Regex.Replace(fileContents, @"([^\^,\r\n])""([^$,\r\n])", @"");
using (CsvReader csv =
       new CsvReader(new StringReader(fixedContents), true))
{
       // ... parse the CSV
7
répondu Mitch Wheat 2009-04-10 03:34:39

je vous recommande de regarder les TextFieldParserClass.Net. Vous devez inclure

Imports Microsoft.VisualBasic.FileIO.TextFieldParser
        Dim afile As FileIO.TextFieldParser = New FileIO.TextFieldParser(FileName)
        Dim CurrentRecord As String() ' this array will hold each line of data
        afile.TextFieldType = FileIO.FieldType.Delimited
        afile.Delimiters = New String() {","}
        afile.HasFieldsEnclosedInQuotes = True

        ' parse the actual file
        Do While Not afile.EndOfData
            Try
                CurrentRecord = afile.ReadFields
            Catch ex As FileIO.MalformedLineException
                Stop
            End Try
        Loop
72
répondu Avi 2009-04-10 03:42:37

essayez ce site. http://kbcsv.codeplex.com/

j'ai cherché un bon utilitaire, et c'est de loin le meilleur que j'ai trouvé et fonctionne correctement. Ne perdez pas votre temps à essayer d'autres choses,c'est gratuit et ça fonctionne.

11
répondu Middletone 2009-09-05 05:03:02

comme le dit ce lien... ne roulez pas votre propre CSV parser!

utilisez TextFieldParser comme suggéré par Avi. Microsoft a déjà fait pour vous. Si vous avez fini par en écrire un, et que vous trouvez un bogue, envisagez de le remplacer au lieu de corriger le bogue. Je l'ai fait récemment et ça m'a fait gagner beaucoup de temps.

7
répondu stone 2009-12-20 06:18:33

donnez un coup d'oeil à la FileHelpers library.

5
répondu CMS 2009-04-10 03:31:05

Vous pourriez donner CsvHelper (une bibliothèque que je maintiens) de l'essayer et il est disponible via NuGet. Il suit l' RFC 4180 standard pour CSV. Il sera capable de gérer n'importe quel contenu à l'intérieur d'un champ, y compris les virgules, les guillemets et les nouvelles lignes.

CsvHelper est simple à utiliser, mais il est aussi facile de le configurer pour qu'il fonctionne avec de nombreux types de fichiers délimités.

CsvReader csv = new CsvReader( streamToFile );
IEnumerable<MyObject> myObjects = csv.GetRecords<MyObject>();

si vous voulez lire des fichiers CSV à un niveau inférieur, vous pouvez utilisez l'analyseur directement, qui retournera chaque ligne comme un tableau de chaîne.

var parser = new CsvParser( myTextReader );
while( true )
{
    string[] line = parser.ReadLine();
    if( line == null )
    {
        break;
    }
}
5
répondu Josh Close 2017-11-30 03:20:58

j'ai écris cela comme une réponse pour que je puisse expliquer comment je l'ai fait et pourquoi.... La réponse de Mitch Wheat a été celle qui m'a donné la meilleure solution pour ce cas et j'ai juste dû la modifier légèrement en raison du format dans lequel ces données ont été exportées.

voici le Code VB:

Dim fixedContents As String = Regex.Replace(
                            File.ReadAllText(csvFile, fileEncoding),
                            "(?<!,)("")(?!,)", 
                            AddressOf ReplaceQuotes)

Le RegEx qui a été utilisé est ce que j'avais besoin de changer parce que certains champs avaient des citations non-échappées et le RegEx fourni ne semblait pas fonctionner sur tous les exemples. Celui-ci utilise "Regarder devant" et "regarder derrière" pour voir si la citation est juste après une virgule ou juste avant. Dans ce cas, ils sont tous les deux négatifs (ce qui signifie Me montrer où la double citation n'est pas avant ou après une virgule). Cela signifie que la citation est dans le milieu d'une corde.

dans ce cas, au lieu de faire un remplacement direct, j'utilise la fonction ReplaceQuotes pour gérer ça pour moi. La raison pour laquelle j'utilise ceci est parce que j'avais besoin d'un peu plus de logique pour détecter si c'était à la début d'une ligne. Si j'avais passé encore plus de temps dessus, je suis sûr que J'aurais pu modifier le RegEx pour prendre en compte le début de la ligne (en utilisant le multiligne, etc) mais quand je l'ai essayé rapidement, il ne semblait pas fonctionner du tout.

avec ceci en place, en utilisant le lecteur CSV sur un fichier CSV 32Mo (environ 19000 lignes), il faut environ 2 secondes pour lire le fichier, effectuer le regex, le charger dans le lecteur CSV, ajouter toutes les données à ma classe générique et terminer. Réel rapide de!!

1
répondu hacker 2009-04-13 13:15:57

RegEx pour exclure la première et la dernière citation serait (?<!^)(?<!,)("")(?!,)(?!$). Bien sûr, vous devez utiliser RegexOptions.Multiligne.

de cette façon, il n'y a pas besoin de fonction d'évaluateur. Mon code remplace les doubles guillemets indésirables par des guillemets simples.

le code C complet est comme ci-dessous.

string fixedCSV = Regex.Replace(
            File.ReadAllText(fileName),
            @"(?<!^)(?<!;)("")(?!;)(?!$)", "'", RegexOptions.Multiline);
1
répondu mariob 2012-11-03 07:44:11

il y a au moins des pilotes ODBC pour les fichiers CSV. Mais il y a différentes saveurs de CSV.

Qu'est-ce qui a produit ces fichiers? Il n'est pas improbable qu'il y ait un pilote correspondant basé sur les exigences de l'application source.

0
répondu dkretz 2009-04-10 03:24:18

votre problème avec CSVReader est que la citation dans le troisième disque n'est pas échappée avec une autre citation (aka double citation). Si vous ne leur échappent pas, alors comment voulez-vous attendre à gérer ", au milieu d'un champ de texte?

http://en.wikipedia.org/wiki/Comma-separated_values

(j'ai fini par avoir à travailler avec des fichiers (avec différents délimiteurs) mais les caractères de citation à l'intérieur d'une valeur de texte n'ont pas été échappés et j'ai fini par écrire ma propre coutume analyseur. Je ne sais pas si c'était absolument nécessaire ou pas.)

0
répondu llamaoo7 2009-04-10 03:31:09

la logique de cette approche personnalisée est: lire le fichier 1 ligne à la fois, diviser chaque ligne sur la virgule, supprimer le premier et le dernier caractère (supprimer les guillemets extérieurs mais n'affectant pas les guillemets intérieurs), puis Ajouter les données à votre liste générique. C'est court et très facile à lire et à travailler avec.

        Dim fr As StreamReader = Nothing
        Dim FileString As String = ""
        Dim LineItemsArr() as String

        Dim FilePath As String = HttpContext.Current.Request.MapPath("YourFile.csv")

        fr = New System.IO.StreamReader(FilePath)

        While fr.Peek <> -1
            FileString = fr.ReadLine.Trim

            If String.IsNullOrEmpty(FileString) Then Continue While 'Empty Line

            LineItemsArr = FileString.Split(",")

            For Each Item as String In LineItemsArr
                'If every item will have a beginning and closing " (quote) then you can just
                'cut the first and last characters of the string here.
                'i.e.  UpdatedItems = Item. remove first and last character

                'Then stick the data into your Generic List (Of String()?)
            Next
        End While
0
répondu rvarcher 2009-04-10 22:00:08
        public static Encoding GetFileEncoding(String fileName)
    {
        Encoding Result = null;
        FileInfo FI = new FileInfo(fileName);
        FileStream FS = null;

        try
        {
            FS = FI.OpenRead();
            Encoding[] UnicodeEncodings = { Encoding.BigEndianUnicode, Encoding.Unicode, Encoding.UTF8 };
            for (int i = 0; Result == null && i < UnicodeEncodings.Length; i++)
            {
                FS.Position = 0;
                byte[] Preamble = UnicodeEncodings[i].GetPreamble();
                bool PreamblesAreEqual = true;
                for (int j = 0; PreamblesAreEqual && j < Preamble.Length; j++)
                {
                    PreamblesAreEqual = Preamble[j] == FS.ReadByte();
                }
                if (PreamblesAreEqual)
                {
                    Result = UnicodeEncodings[i];
                }
            }
        }
        catch (System.IO.IOException)
        {
        }
        finally
        {
            if (FS != null)
            {
                FS.Close();
            }
        }

        if (Result == null)
        {
            Result = Encoding.Default;
        }

        return Result;
    }
0
répondu Daver 2011-01-17 20:16:03