CSV Kopfzeilenloop



  • Hallo zusammen, ich habe folgende Aufgabe/ folgendes Problem.
    Ich soll eine csv Datei einlesen und formatiert ausgeben.
    CSV Bsp:

    Name:,Vorname:,Strasse:,PLZ:,Telefonnummer:
    Mustermann,Max,Teststraße 123,12345,0123 456789
    Musterfrau,Maria, ...
    

    Ausgabe Bsp:

    Name: Mustermann
    Vorname: Max
    Straße: Teststraße
    ...
    

    und das ganze dann immer mit Kopfzeile der csv Datei vorangestellt für das jeweilige Element.
    Wie man jetzt an meinem bisherigen Code vermuten kann habe ich erstmal versucht mir die Kopfzeile als eigenen String zu nehmen um damit zu testen wie ich den loop hinbekomme ohne mir gedanken über die csv Datei machen zu müssen und habe einfach die Kopfzeile Zeile mit dem

    fgets(str, 39, fp);
    

    versucht zu überspringen.

    	FILE* fp;
    	char fline[1000] = "Name:,Vorname:,Strasse:,PLZ:,Telefonnummer:";
    	char str[500]{};
    	fp = fopen("C:\\Test.csv", "r");
    	if (fp == NULL) {
    		printf("Datei konnte NICHT geoeffnet werden.\n");
    		return 0;
    	}
    	else {
    		printf("Datei geoeffnet.\n");
    		fgets(str, 39, fp);
    		while (fgets(str, sizeof(str), fp))
    		{
    			char *seperated = strtok(str, ",");
    			char *sep2 = strtok(fline, ",");
    			while (seperated)
    			{
    				
    				printf("%s \t%s\n", sep2, seperated);
    
    				seperated = strtok(NULL, ",");
    				sep2 = strtok(NULL, ",");
    			}
    			printf("\n");
    		}
    		fclose(fp);
    	}
    

    Meine jetzige ausgabe sieht aber momentan eher so aus:

    Datei geoeffnet.
    Name:   Mustermann
    Strasse:        Vorname:
    Telefonnummer:  PLZ:
    


  • Die Datenzeilen enthalten doch nur die Daten und nicht noch den Attributnamen, damit ist das paarweise Auslesen mit Sicherheit nicht richtig.

    Ouch, Moment. Hab grad erst gesehen, dass du strtok mit verschiedenen Parametern aufrufst.
    strtok merkt sich intern den aktuellen "Arbeitsstring", d.h. du kannst gleichzeitig immer nur einen String parsen und darfst das nicht mischen. Jetzt hast du zwei Möglichkeiten:

    1. du benutzt ein festes Schema und gibst die Nutzdaten gemäß dieses Schemas aus (Name, Vorname, Strasse, PLZ, Telefonnummer)
    2. du parst die Kopfzeile ein mal und merkst dir die Überschrift der jeweiligen Spalte (Wichtig: als Kopie, nicht als Zeiger!). Bei der Ausgabe musst du dann einen Index mitlaufen lassen, der dir sagt, in welcher Spalte du dich gerade befindest und gibst dann die Spaltenüberschrift mit den Nutzdaten aus.


  • @DocShoe
    Ja, dass ist ein überbleibsel von meinen verschiedenen Ideen, ursprünglich war es so,

    (...)
    		while (fgets(str, sizeof(str), fp))
    		{
    			char *seperated = strtok(str, ",");
    			while (seperated)
    			{
    				printf("%s \t%s\n", fline, seperated);
    				seperated = strtok(NULL, ",");
    			}
    			printf("\n");
    		}
    		fclose(fp);
    	}
    

    wobei dann natürlich das Problem ist, dass ich nicht die einzelnen Elemente der Kopfzeile ausgebe sondern nur den kompletten String.

    Name:,Vorname:,Strasse:,PLZ:,Telefonnummer:     Mustermann
    Name:,Vorname:,Strasse:,PLZ:,Telefonnummer:     Max
    ...
    


  • @DocShoe Danke für die Tipps (auch wenn ich aus deiner ersten Möglichkeit nicht schlau werde). Ich habe es jetzt mal mit dem Vorschlag nach Möglichkeit 2 etwas umgeschrieben und die Ausgabe funktioniert soweit.

    (...)
    		printf("Datei geoeffnet.\n");
    		fgets(str, 39, fp);
    		arr[i] = strtok(fline, ",");
    		while (arr[i] != NULL)
    		{
    			arr[++i] = strtok(NULL, ",");
    		}
    		while (fgets(str, sizeof(str), fp))
    		{
    			int icounter = 0;
    			
    			char *seperated = strtok(str, ",");
    			while (seperated)
    			{
    				printf("%s \t%s\n", arr[icounter], seperated);
    				icounter = icounter + 1 % 5;
    				seperated = strtok(NULL, ",");
    			}
    			printf("\n");
    (...)
    

    Ist das so i.O. oder ist da irgendwas was man so besser nicht machen sollte?



  • @Reche

    Soweit ich das sehen kann sieht das gut aus. Ich würde nur das icounter = icounter + 1 % 5; in Zeile 17 nicht machen, sondern den Attributnamen per Fallunterscheidung ausgeben. Und separated statt seperated benutzen 😉

    ...
    if( icounter < 5 )
    {
       printf("%s \t%s\n", arr[icounter], seperated);
    }
    else
    {
       printf("Unbekannt: \t%s\n", seperated);
    }
    

    Mit meiner ersten Möglichkeit meinte ich, dass die Spaltenüberschriften fest sind und du davon ausgehst, dass sich das CSV-Datei Format nicht ändert. Statt dein arr aus der Datei zu lesen legst du es einfach fest:

    char const* arr[] = {
       "Name",
       "Vorname",
       "Strasse",
       "PLZ",
       "Telefonnummer"
    };
    


  • @DocShoe
    Alles klar, danke für die Erklärung und Hilfe.👍



  • Und wenn ich jetzt bei Straße eintrage:
    Hauptstraße 54, Haus 3
    dann ist auf einmal alles kaputt 🙂

    Ich hoffe, dass du das nur eine Lernaufgabe ist und kein reales Problem?



  • @wob Ist nur eine Lernaufgabe, darfst aber gerne elaborieren. Ich nehme alle informationen mit die ich bekommen kann.



  • @Reche
    Das Problem tritt auf, wenn das Trennzeichen selbst Bestandteil der Daten ist, dann darf es nicht als Trennzeichen behandelt werden. Üblicherweise benutzt man dann Escape-Sequenzen, um das zu kennzeichnen, dann brauchst du aber eigene Tokenizer-Funktion, da nicht jedes Trennzeichen-Zeichen als Trennzeichen behandelt werden darf. Bei CSV-Dateien ist es auch üblich, einzelne Daten in Hochkommata einzufassen, und alles innerhalb der Hochkommata als Daten zu verwenden.

    Beispiel:

    // Fehler, "Hauptstraße 54, Haus 3" ist ein Token und gehört zusammen
    Max Mustermann, Hauptstraße 54, Haus 3, 12321, 0123/4567 
    
    // Eine Lösung, Daten werden spaltenweise in Hochkommata eingefasst, Parsen ist schwieriger
    "Max Mustermann", "Hauptstraße 54, Haus 3", "12321", "0123/4567" 
    
    // andere Lösung, Trennzeichen wird mit \ eingeleitet und darf nicht wie ein Trennzeichen behandelt werden
    Max Mustermann, Hauptstraße 54\, Haus 3, 12321, 0123/4567 
    

  • Mod

    @DocShoe sagte in CSV Kopfzeilenloop:

    Max Mustermann, Haupstraße 54

    Weitere Verkomplizierung: Ist das da nun die "Hauptstraße" oder die " Hauptstraße"?



  • @SeppJ sagte in CSV Kopfzeilenloop:

    @DocShoe sagte in CSV Kopfzeilenloop:

    Max Mustermann, Haupstraße 54

    Weitere Verkomplizierung: Ist das da nun die "Hauptstraße" oder die " Hauptstraße"?

    Keine Ahnung, wovon du redest 😉

    Edit:
    Lol, hatte da nochn Rechtschreibfehler drin, ich dachte, du spielst auf den an. Iwo stand da auch mal "Haupstraße". Aber ´ne trim() Funktion ist doch trivial.



  • @DocShoe Verstehe, das macht das ganze natürlich noch komplizierter und anfälliger, da muss ich wohl noch einige Fehler abfangen.
    @SeppJ Ja, Whitespaces sind auch immer so eine Sache 🙂



  • @DocShoe sagte in CSV Kopfzeilenloop:

    Aber ´ne trim() Funktion ist doch trivial.

    Schon, aber es könnte auch sein, dass man nicht trimmen darf, weil führende Leerzeichen eine Bedeutung haben könnten (bestes Beispiel: der Formatstring in scanf) - gut, bei Adressen ist das kein Problem.

    In der Praxis habe ich es oft mit csv-Dateien zu tun, die entweder Trennzeichen auch im Text nutzen oder zwar mit "..." quoten, wo dann aber doch alle paar tausend Zeilen auch mal " in den Daten vorkommt (ohne Sondermarkierung). Und dann beschwert man sich beim Kunden und der nimmt einfach Tab statt Komma - nur damit ich dann feststelle, dass irgendein Freitextfeld im csv auch Tabs enthalten kann (und das auch vorkommt). Daher: csv selbst machen geht fast immer irgendwie schief.


  • Gesperrt

    Hier mal das Parsen nur der Kopfzeile (ansi-c, pedantisch):

    #define _GNU_SOURCE
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define COLS 5
    
    char header[COLS][100];
    
    int readHeader(char *fn)
    {
        FILE *fp;
        char *line = NULL;
        size_t len = 0;
        ssize_t read;
        char *ptr = NULL;
        size_t i = 0;
        int ret = 1;
    
        fp = fopen(fn, "r");
        if (fp == NULL)
        {
            ret = 0;
            return ret;
        }
    
        read = getline(&line, &len, fp);
        if (read != -1)
        {
            ptr = strtok(line, ",");
            while (i < COLS && ptr != NULL && strlen(ptr) < 100)
            {
                printf(" found:%s\n", ptr);
                strcpy(header[i], ptr);
                ptr = strtok(NULL, ",");
                i++;
            }
            if (i != COLS || ptr != NULL)
            {
                ret = 0;
            }
        }
        else
        {
            ret = 0;
        }
    
        fclose(fp);
        if (line)
        {
            free(line);
        }
    
        return ret;
    }
    
    int main(void)
    {
        size_t i = 0;
    
        if (readHeader("mycsv.txt"))
        {
            while (i < COLS)
            {
                printf("%s\n", header[i++]);
            }
        }
    
        exit(EXIT_SUCCESS);
    }
    

    Geholfen hat mir dabei: https://www.c-howto.de/tutorial/strings-zeichenketten/string-funktionen/string-zerteilen/

    Jetzt brauchst du nur noch eine Parse-Funktion für den Tabellenkörper. Die ist eigentlich ähnlich.


  • Gesperrt

    Wieso sagt keiner mehr was? Das lässt ja fast den Verdacht zu, dass alles richtig wäre ...



  • @TaucherOne
    ssize_t ist kein ANSI C
    getline ist kein ANSI C
    &line ist UB
    strtok benutzt man nicht sondern fgets+sscanf
    u.v.a.m.


  • Mod

    Damit das allen klar ist: TaucherOne ist die neueste Inkarnation unseres ansässigen Trolls. Daher folgt in dem Beitrag auf "ansi-c, pedantisch" direkt als erstes #define _GNU_SOURCE (Das ist meiner Meinung nach sogar ein schöner Touch. Echte Qualitätstrollerei. Darauf muss man erst einmal kommen). Der will keine echte Kritik, sondern nur Aufmerksamkeit.


  • Gesperrt

    @Wutz Kritik zu äußern, ist das eine. Alternativen zu bilden, das andere. Oder anders: Welchen Mehrwert hat der TO von deinem Beitrag jetzt? 😉

    Du willst auf getline verzichten? Dann wünsche ich auf jeden Fall viel Erfolg dabei.



  • @TaucherOne
    Hat er doch geschrieben, zB fgets statt getline. Der Hinweis auf Undefined Behaviour eines Codeschnipsel hat gewaltigen Mehrwert. Finde ich zumindest. Muss halt jeder selbst entscheiden, ob er damit leben kann, dass ihm seine Anwendung jederzeit um die Ohren fliegen kann.


  • Gesperrt

    @DocShoe sagte in CSV Kopfzeilenloop:

    Der Hinweis auf Undefined Behaviour eines Codeschnipsel hat gewaltigen Mehrwert.

    Wenn das zutreffen würde (UB), wäre ich ganz bei dir.