54

I have a .csv file:

lp;imie;nazwisko;ulica;numer;kod;miejscowosc;telefon;email;data_ur
1;Jan;Kowalski;ul. Nowa;1a;11-234;Budry;123-123-456;jan@go.xxx;1980.05.13
2;Jerzy;Nowak;ul. Konopnicka;13a/3;00-900;Lichowice;(55)333-44-55;jer@wu.to;1990.03.23

And I need to read this in C. I have some code, but only for the connection.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Zesss
  • 585
  • 1
  • 5
  • 9
  • 4
    C CSV Parser: http://sourceforge.net/projects/cccsvparser C CSV Writer: http://sourceforge.net/projects/cccsvwriter – SomethingSomething Aug 23 '14 at 21:26
  • 3
    Please [edit] your question to show [the code you have so far](http://whathaveyoutried.com). You should include at least an outline (but preferably a [mcve]) of the code that you are having problems with, then we can try to help with the specific problem. You should also read [ask]. – Toby Speight Mar 15 '18 at 14:24
  • fast with a lot of examples: https://github.com/liquidaty/zsv – mwag Jan 18 '22 at 07:40
  • The sample input data is separated by semicolons. [CSV](https://en.wikipedia.org/wiki/Comma-separated_values) - *"The term "CSV" also denotes several closely-related delimiter-separated formats that use other field delimiters such as semicolons."* – Peter Mortensen Mar 07 '22 at 18:28

6 Answers6

73

Hopefully this would get you started

See it live on http://ideone.com/l23He (using stdin)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char* getfield(char* line, int num)
{
    const char* tok;
    for (tok = strtok(line, ";");
            tok && *tok;
            tok = strtok(NULL, ";\n"))
    {
        if (!--num)
            return tok;
    }
    return NULL;
}

int main()
{
    FILE* stream = fopen("input", "r");

    char line[1024];
    while (fgets(line, 1024, stream))
    {
        char* tmp = strdup(line);
        printf("Field 3 would be %s\n", getfield(tmp, 3));
        // NOTE strtok clobbers tmp
        free(tmp);
    }
}

Output:

Field 3 would be nazwisko
Field 3 would be Kowalski
Field 3 would be Nowak
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
sehe
  • 374,641
  • 47
  • 450
  • 633
  • 10
    Since `strtok` cannot handle blank nodes, how would you approach an input line such as `"A1,B2,C3,,F5,G6"` I'm using a combination of `strchr` and `strcpy` but Im having trouble getting the 'G6' value. – ProfessionalAmateur Sep 17 '14 at 20:09
  • 2
    @ProfessionalAmateur I'd be using C++. Sorry. See [c++ answers about csv](http://stackoverflow.com/search?tab=votes&q=user%3a85371%20%5bc%2b%2b%5d%20csv) – sehe Sep 17 '14 at 21:20
  • 3
    Let's call them "tokens", not "nodes". – Lightness Races in Orbit Sep 17 '14 at 21:26
  • 1
    `Tokens` is correct, spending too much time with XML. – ProfessionalAmateur Sep 17 '14 at 21:29
  • 2
    There is a simple function I use. Check zstrtok() function. https://github.com/fnoyanisi/zString – fnisi May 21 '15 at 09:57
  • It doesn't handle quoting, either. – ivan_pozdeev Jan 23 '17 at 04:50
  • 1
    This can't handle 11922;28;;1. – cokedude Oct 25 '17 at 11:28
  • @cokedude you mean it doesn't do what you expect. And rightfully so. I already pointed to my answers about that in my comment of 2014: https://stackoverflow.com/search?tab=votes&q=user%3a85371%20%5bc%2b%2b%5d%20csv – sehe Oct 25 '17 at 11:32
  • What does `tok = strtok(NULL, ";\n")` mean? What is the result of it? – user Dec 04 '18 at 23:01
  • @user It's a call to the function [`strtok`](http://pubs.opengroup.org/onlinepubs/009695299/functions/strtok.html) and it continues the tokenization as documented in that link: _"A sequence of calls to strtok() breaks the string pointed to by s1 into a sequence of tokens, each of which is delimited by a byte from the string pointed to by s2. The first call in the sequence has s1 as its first argument, and is followed by calls with a null pointer as their first argument. The separator string pointed to by s2 may be different from call to call._" – sehe Dec 04 '18 at 23:22
  • Thanks for this answer @sehe. There are 3 lines of code that I have trouble to understand: 1. for the line `for (tok = strtok(line, ";");` what is the incrementer? 2. for the line `tok && *tok;` does this change the value of `tok` and if yes by which mechanism? and finally 3. : `if (!--num)` is testing wether `num` is zero, decrementing it and stopping when `num` is zero? Many thanks in advance! – ecjb Oct 31 '22 at 17:18
  • 1
    @ecjb the structure of the for-loop statement is `for(initialization; condition; increment) { statements; }`. So in that example ***1.*** the incrementer is `tok = strtok(NULL, ",\n")` (see [docs](https://pubs.opengroup.org/onlinepubs/007904975/functions/strtok.html#:~:text=Each%20subsequent%20call%2C%20with%20a%20null%20pointer%20as%20the%20value%20of%20the%20first%20argument%2C%20starts%20searching%20from%20the%20saved%20pointer%20and%20behaves%20as%20described%20above). ***2.*** Does *not* change `tok` (obviously); This makes sense because it's the condition of the for loop. – sehe Oct 31 '22 at 22:36
  • 1
    ***3.*** Yes, but mind the order: it's decrementing `num`, then returning `tok` if `num==0`. – sehe Oct 31 '22 at 22:37
  • Why are you checking whether the first character in `tok` is not `\0` in `tok && *tok`? The manual already states that `strtok` returns non-empty tokens: *"The strtok() function breaks a string into a sequence of zero or more **nonempty*** tokens." – Mehdi Charife Mar 26 '23 at 00:45
  • 1
    @MehdiCharife good point. I guess I still like that now the semantics of the function are clear without having to go to the documentation of `strtok`. But in all honesty, I would reject any code ever using `strtok` in 2023 – sehe Mar 26 '23 at 01:11
  • @sehe What would you recommend we use instead? – Mehdi Charife Mar 26 '23 at 01:14
  • @MehdiCharife C++ :) Or any non-mutating implementation instead. At the very least [a version that doesn't use static state](https://pubs.opengroup.org/onlinepubs/9699919799/functions/strtok.html#:~:text=The%20strtok_r()%20function%20is,unrelated%20call%20from%20another%20thread.). – sehe Mar 26 '23 at 01:32
  • Why you didn't close the stream (`fclose`) after finishing from it? – iTzVoko Aug 10 '23 at 08:13
10

The following code is in plain c language and handles blank spaces. It only allocates memory once, so one free() is needed, for each processed line.

http://ideone.com/mSCgPM

/* Tiny CSV Reader */
/* Copyright (C) 2015, Deligiannidis Konstantinos

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://w...content-available-to-author-only...u.org/licenses/>.  */


#include <stdio.h>
#include <string.h>
#include <stdlib.h>


/* For more that 100 columns or lines (when delimiter = \n), minor modifications are needed. */
int getcols( const char * const line, const char * const delim, char ***out_storage )

{
const char *start_ptr, *end_ptr, *iter;
char **out;
int i;                                          //For "for" loops in the old c style.
int tokens_found = 1, delim_size, line_size;    //Calculate "line_size" indirectly, without strlen() call.
int start_idx[100], end_idx[100];   //Store the indexes of tokens. Example "Power;": loc('P')=1, loc(';')=6
//Change 100 with MAX_TOKENS or use malloc() for more than 100 tokens. Example: "b1;b2;b3;...;b200"

if ( *out_storage != NULL )                 return -4;  //This SHOULD be NULL: Not Already Allocated
if ( !line || !delim )                      return -1;  //NULL pointers Rejected Here
if ( (delim_size = strlen( delim )) == 0 )  return -2;  //Delimiter not provided

start_ptr = line;   //Start visiting input. We will distinguish tokens in a single pass, for good performance.
                    //Then we are allocating one unified memory region & doing one memory copy.
while ( ( end_ptr = strstr( start_ptr, delim ) ) ) {

    start_idx[ tokens_found -1 ] = start_ptr - line;    //Store the Index of current token
    end_idx[ tokens_found - 1 ] = end_ptr - line;       //Store Index of first character that will be replaced with
                                                        //'\0'. Example: "arg1||arg2||end" -> "arg1\0|arg2\0|end"
    tokens_found++;                                     //Accumulate the count of tokens.
    start_ptr = end_ptr + delim_size;                   //Set pointer to the next c-string within the line
}

for ( iter = start_ptr; (*iter!='\0') ; iter++ );

start_idx[ tokens_found -1 ] = start_ptr - line;    //Store the Index of current token: of last token here.
end_idx[ tokens_found -1 ] = iter - line;           //and the last element that will be replaced with \0

line_size = iter - line;    //Saving CPU cycles: Indirectly Count the size of *line without using strlen();

int size_ptr_region = (1 + tokens_found)*sizeof( char* );   //The size to store pointers to c-strings + 1 (*NULL).
out = (char**) malloc( size_ptr_region + ( line_size + 1 ) + 5 );   //Fit everything there...it is all memory.
//It reserves a contiguous space for both (char**) pointers AND string region. 5 Bytes for "Out of Range" tests.
*out_storage = out;     //Update the char** pointer of the caller function.

//"Out of Range" TEST. Verify that the extra reserved characters will not be changed. Assign Some Values.
//char *extra_chars = (char*) out + size_ptr_region + ( line_size + 1 );
//extra_chars[0] = 1; extra_chars[1] = 2; extra_chars[2] = 3; extra_chars[3] = 4; extra_chars[4] = 5;

for ( i = 0; i < tokens_found; i++ )    //Assign adresses first part of the allocated memory pointers that point to
    out[ i ] = (char*) out + size_ptr_region + start_idx[ i ];  //the second part of the memory, reserved for Data.
out[ tokens_found ] = (char*) NULL; //[ ptr1, ptr2, ... , ptrN, (char*) NULL, ... ]: We just added the (char*) NULL.
                                                    //Now assign the Data: c-strings. (\0 terminated strings):
char *str_region = (char*) out + size_ptr_region;   //Region inside allocated memory which contains the String Data.
memcpy( str_region, line, line_size );   //Copy input with delimiter characters: They will be replaced with \0.

//Now we should replace: "arg1||arg2||arg3" with "arg1\0|arg2\0|arg3". Don't worry for characters after '\0'
//They are not used in standard c lbraries.
for( i = 0; i < tokens_found; i++) str_region[ end_idx[ i ] ] = '\0';

//"Out of Range" TEST. Wait until Assigned Values are Printed back.
//for ( int i=0; i < 5; i++ ) printf("c=%x ", extra_chars[i] ); printf("\n");

// *out memory should now contain (example data):
//[ ptr1, ptr2,...,ptrN, (char*) NULL, "token1\0", "token2\0",...,"tokenN\0", 5 bytes for tests ]
//   |__________________________________^           ^              ^             ^
//          |_______________________________________|              |             |
//                   |_____________________________________________|      These 5 Bytes should be intact.

return tokens_found;
}


int main()

{

char in_line[] = "Arg1;;Th;s is not Del;m;ter;;Arg3;;;;Final";
char delim[] = ";;";
char **columns;
int i;

printf("Example1:\n");
columns = NULL; //Should be NULL to indicate that it is not assigned to allocated memory. Otherwise return -4;

int cols_found = getcols( in_line, delim, &columns);
for ( i = 0; i < cols_found; i++ ) printf("Column[ %d ] = %s\n", i, columns[ i ] );  //<- (1st way).
// (2nd way) // for ( i = 0; columns[ i ]; i++) printf("start_idx[ %d ] = %s\n", i, columns[ i ] );

free( columns );    //Release the Single Contiguous Memory Space.
columns = NULL;     //Pointer = NULL to indicate it does not reserve space and that is ready for the next malloc().

printf("\n\nExample2, Nested:\n\n");

char example_file[] = "ID;Day;Month;Year;Telephone;email;Date of registration\n"
        "1;Sunday;january;2009;123-124-456;jitter@go.xyz;2015-05-13\n"
        "2;Monday;March;2011;(+30)333-22-55;buffer@wl.it;2009-05-23";

char **rows;
int j;

rows = NULL; //getcols() requires it to be NULL. (Avoid dangling pointers, leaks e.t.c).

getcols( example_file, "\n", &rows);
for ( i = 0; rows[ i ]; i++) {
    {
        printf("Line[ %d ] = %s\n", i, rows[ i ] );
        char **columnX = NULL;
        getcols( rows[ i ], ";", &columnX);
        for ( j = 0; columnX[ j ]; j++) printf("  Col[ %d ] = %s\n", j, columnX[ j ] );
        free( columnX );
    }
}

free( rows );
rows = NULL;

return 0;
}
user5175223
  • 101
  • 1
  • 2
3

A complete example which leaves the fields as NULL-terminated strings in the original input buffer and provides access to them via an array of char pointers. The CSV processor has been confirmed to work with fields enclosed in "double quotes", ignoring any delimiter chars within them.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// adjust BUFFER_SIZE to suit longest line 
#define BUFFER_SIZE 1024 * 1024
#define NUM_FIELDS 10
#define MAXERRS 5
#define RET_OK 0
#define RET_FAIL 1
#define FALSE 0
#define TRUE 1

// char* array will point to fields
char *pFields[NUM_FIELDS];
// field offsets into pFields array:
#define LP          0
#define IMIE        1
#define NAZWISKo    2
#define ULICA       3
#define NUMER       4
#define KOD         5
#define MIEJSCOw    6
#define TELEFON     7
#define EMAIL       8
#define DATA_UR     9

long loadFile(FILE *pFile, long *errcount);
static int  loadValues(char *line, long lineno);
static char delim;

long loadFile(FILE *pFile, long *errcount){

    char sInputBuf [BUFFER_SIZE];
    long lineno = 0L;

    if(pFile == NULL)
        return RET_FAIL;

    while (!feof(pFile)) {

        // load line into static buffer
        if(fgets(sInputBuf, BUFFER_SIZE-1, pFile)==NULL)
            break;

        // skip first line (headers)
        if(++lineno==1)
            continue;

        // jump over empty lines
        if(strlen(sInputBuf)==0)
            continue;
        // set pFields array pointers to null-terminated string fields in sInputBuf
        if(loadValues(sInputBuf,lineno)==RET_FAIL){
           (*errcount)++;
            if(*errcount > MAXERRS)
                break;
        } else {    
            // On return pFields array pointers point to loaded fields ready for load into DB or whatever
            // Fields can be accessed via pFields, e.g.
            printf("lp=%s, imie=%s, data_ur=%s\n", pFields[LP], pFields[IMIE], pFields[DATA_UR]);
        }
    }
    return lineno;
}


static int  loadValues(char *line, long lineno){
    if(line == NULL)
        return RET_FAIL;

    // chop of last char of input if it is a CR or LF (e.g.Windows file loading in Unix env.)
    // can be removed if sure fgets has removed both CR and LF from end of line
    if(*(line + strlen(line)-1) == '\r' || *(line + strlen(line)-1) == '\n')
        *(line + strlen(line)-1) = '\0';
    if(*(line + strlen(line)-1) == '\r' || *(line + strlen(line)-1 )== '\n')
        *(line + strlen(line)-1) = '\0';

    char *cptr = line;
    int fld = 0;
    int inquote = FALSE;
    char ch;

    pFields[fld]=cptr;
    while((ch=*cptr) != '\0' && fld < NUM_FIELDS){
        if(ch == '"') {
            if(! inquote)
                pFields[fld]=cptr+1;
            else {
                *cptr = '\0';               // zero out " and jump over it
            }
            inquote = ! inquote;
        } else if(ch == delim && ! inquote){
            *cptr = '\0';                   // end of field, null terminate it
            pFields[++fld]=cptr+1;
        }
        cptr++;
    }   
    if(fld > NUM_FIELDS-1){
        fprintf(stderr, "Expected field count (%d) exceeded on line %ld\n", NUM_FIELDS, lineno);
        return RET_FAIL;
    } else if (fld < NUM_FIELDS-1){
        fprintf(stderr, "Expected field count (%d) not reached on line %ld\n", NUM_FIELDS, lineno);
        return RET_FAIL;    
    }
    return RET_OK;
}

int main(int argc, char **argv)
{
   FILE *fp;
   long errcount = 0L;
   long lines = 0L;

   if(argc!=3){
       printf("Usage: %s csvfilepath delimiter\n", basename(argv[0]));
       return (RET_FAIL);
   }   
   if((delim=argv[2][0])=='\0'){
       fprintf(stderr,"delimiter must be specified\n");
       return (RET_FAIL);
   }
   fp = fopen(argv[1] , "r");
   if(fp == NULL) {
      fprintf(stderr,"Error opening file: %d\n",errno);
      return(RET_FAIL);
   }
   lines=loadFile(fp,&errcount);
   fclose(fp);
   printf("Processed %ld lines, encountered %ld error(s)\n", lines, errcount);
   if(errcount>0)
        return(RET_FAIL);
    return(RET_OK); 
}
Gus Gator
  • 31
  • 1
  • 2
    The accepted answer will treat this 4-element CSV as 6 elements: `QA-Q000630115728222,QA-A0926511569122067,"In 1687 John Phillips, Miltons nephew, produced a Don Quixote made English.",2017-03-07T00:00:00.000Z` Gus Gator's example will treat it as the proper 4 elements. – Adam Howell Dec 14 '18 at 18:31
3

Use fscanf to read the file until you encounter ';' or \n, then just skip it with fscanf(f, "%*c").

int main()
{
    char str[128];
    int result;
    FILE* f = fopen("test.txt", "r");

    /*...*/

    do {
        result = fscanf(f, "%127[^;\n]", str);

        if(result == 0)
        {
            result = fscanf(f, "%*c");
        }
        else
        {
            //Put here whatever you want to do with your value.
            printf("%s\n", str);
        }

    } while(result != EOF);

    return 0;
}
WENDYN
  • 650
  • 7
  • 15
2

This code is fairly simple, but effective. It parses comma-separated files with parenthesis. You can easily modify it to suit your needs.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int main(int argc, char *argv[])
{
  // argv[1] path to csv file
  // argv[2] number of lines to skip
  // argv[3] length of longest value (in characters)

  FILE *pfinput;
  unsigned int nSkipLines, currentLine, lenLongestValue;
  char *pTempValHolder;
  int c;
  unsigned int vcpm; // Value character marker
  int QuotationOnOff; // 0 - off, 1 - on

  nSkipLines = atoi(argv[2]);
  lenLongestValue = atoi(argv[3]);

  pTempValHolder = (char*)malloc(lenLongestValue);

  if(pfinput = fopen(argv[1], "r")) {

    rewind(pfinput);

    currentLine = 1;
    vcpm = 0;
    QuotationOnOff = 0;

    // currentLine > nSkipLines condition
    // skips / ignores first argv[2] lines
    while((c = fgetc(pfinput)) != EOF)
    {
       switch(c)
       {
          case ',':
            if(!QuotationOnOff && currentLine > nSkipLines)
            {
              pTempValHolder[vcpm] = '\0';
              printf("%s,", pTempValHolder);
              vcpm = 0;
            }
            break;

          case '\n':
            if(currentLine > nSkipLines)
            {
              pTempValHolder[vcpm] = '\0';
              printf("%s\n", pTempValHolder);
              vcpm = 0;
            }
            currentLine++;
            break;

          case '\"':
            if(currentLine > nSkipLines)
            {
              if(!QuotationOnOff) {
                QuotationOnOff = 1;
                pTempValHolder[vcpm] = c;
                vcpm++;
              } else {
                QuotationOnOff = 0;
                pTempValHolder[vcpm] = c;
                vcpm++;
              }
            }
            break;

          default:
            if(currentLine > nSkipLines)
            {
              pTempValHolder[vcpm] = c;
              vcpm++;
            }
            break;
       }
    }

    fclose(pfinput);
    free(pTempValHolder);
  }

  return 0;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 2
    I'd ask the poster what they mean by 'with parenthesis' but they're no longer a member at SO. The code is pretty clean, but AFAICS it is a complicated way of copying everything from the Nth line to the end of file to the output. – Jonathan Leffler Mar 05 '18 at 02:03
0
#include <conio.h>
#include <stdio.h>
#include <string.h>

// Driver Code
int main()
{
    // Substitute the full file path
    // for the string file_path
    FILE* fp = fopen("Movie.csv", "r");
    char *wrds[40];
    if (!fp)
        printf("Can't open file\n");

    else {
        // Here we have taken size of
        // array 1024 you can modify it
        char buffer[1024];

        int row = 0;
        int column = 0;

        while (fgets(buffer, 1024, fp)) {
            column = 0;
            row++;

            // To avoid printing of column
            // names in file can be changed
            // according to need
            if (row == 1)
                continue;

            // Splitting the data
            char* value = strtok(buffer, ", ");

            while (value) {
                // Column 1
                if (column == 0) {
                    printf("Name :");
                }

                // Column 2
                if (column == 1) {
                   printf("\tAccount No. :");
                }

                // Column 3
                if (column == 2) {
                    printf("\tAmount :");
                }

                printf("%s", value);
                wrds[column] = value;
                value = strtok(NULL, ", ");
                column++;
            }

            printf("\n");
        }

        // Close the file
        fclose(fp);
   }
    getchar();
    return 0;
}