#include "easycsv.h" /* Flags */ typedef enum { EASYCSV_NONE, EASYCSV_STRING, EASYCSV_INT, EASYCSV_UNKNOWN } _EASYCSV_TYPE; typedef enum { /* Generic errors */ EASYCSV_NOERROR = 0, // no error EASYCSV_NULLCSV, // easycsv* is NULL EASYCSV_NULLPTR, // generic pointer is NULL EASYCSV_EMPTYSTRING, // empty string EASYCSV_OVERMAXROW, // int exceeds row limit EASYCSV_OVERMAXCOL, // int exceeds col limit EASYCSV_ZEROROW, EASYCSV_ZEROCOL, /* File input/output errors */ EASYCSV_UNKNOWNMODE, // unknown file IO mode EASYCSV_OPENFAIL, // fail to open EASYCSV_REOPENFAIL, // fail to reopen EASYCSV_EMPTYCSV, // csv is empty or does not exist EASYCSV_UNWRITABLE, // csv mode not at EASYCSV_W EASYCSV_UNREADABLE, // csv mode not at EASYCSV_R EASYCSV_UPDATEFAIL, // update file fail EASYCSV_FILEPTRFAIL, // move file pointer fail /* Non-existant elements */ EASYCSV_ROWNOTEXIST, // row does not exist EASYCSV_COLNOTEXIST, // column does not exist /* Subroutine failure */ EASYCSV_PUSHCOLFAIL, // push column value fail EASYCSV_COLNUMFAIL // column number retrieval fail } EASYCSV_ERROR; typedef struct _easycsv { FILE *file; // original CSV file FILE *temp; // temporary CSV file for writing char *fp; char *tmpfp; unsigned int rows; unsigned int cols; EASYCSV_ERRORMSG error; #ifdef EASYCSV_DEBUG clock_t start; #endif } _easycsv; /* PRIVATE FUNCTIONS */ /* Constructor of private members */ static _easycsv *_easycsv_priv_init(const char*, const EASYCSV_MODE, const EASYCSV_ERRORMSG); /* Destructor of private members */ static void _easycsv_priv_free(_easycsv*); /* Verifies mode of file */ // static int _easycsv_checkmode(struct easycsv*, const char); /* Copies data from temp FILE to file FILE */ static int _easycsv_update(easycsv*); /* Rewind easycsv, checks for readability */ static int _easycsv_rewind(_easycsv*, const EASYCSV_MODE); /* Returns string of a specific row */ static char *_easycsv_getrow(const easycsv*, const unsigned int); /* Return column number of a named column */ static int _easycsv_getcolumn(const easycsv*, const char*); /* Verifies the type of the value (eg: string or int) */ static int _easycsv_checktype(const easycsv*, const int, const int); /* Verifies if there is a value or not */ static int _easycsv_checkifvalue(const easycsv*, const int, const int); /* */ // static char *_easycsv_getvalueinrow(const int, const char*); /* Calculate rows */ static int _easycsv_rows(_easycsv*, const EASYCSV_MODE); /* Calculate columns*/ static int _easycsv_columns(_easycsv*, const EASYCSV_MODE); static void _easycsv_printerror(const _easycsv*, const EASYCSV_ERROR); /* Print error from _easycsv struct in stderr */ static void _easycsv_geterror(const _easycsv*, const EASYCSV_ERROR, FILE*); /* Check if easycsv* and const char* are NULL */ static int _easycsv_checkcsvandstring_one(const easycsv*, const char*); /* Check if easycsv* and two const char* are NULL*/ static int _easycsv_checkcsvandstring_two(const easycsv*, const char*, const char*); /* Verifies if the string is not NULL or empty, returns 0 on success and -1 on failure */ // static int _easycsv_checkstring(const char*); /* Verifies if easycsv is not NULL or unallocated */ // static int _easycsv_checkeasycsv(const struct easycsv*); /* Verifies if int is */ // static int _easycsv_checkunsigned(const int); /* * ==================== * FUNCTION DEFINITIONS * ==================== */ /* PUBLIC FUNCTIONS */ easycsv* easycsv_init(const char *fp, const EASYCSV_MODE mode) { return easycsv_init_errormsg(fp, mode, EASYCSV_ERRORSTDERR); } easycsv* easycsv_init_errormsg(const char *fp, const EASYCSV_MODE mode, const EASYCSV_ERRORMSG error) { #ifdef EASYCSV_DEBUG csv->start = clock(); #endif easycsv *csv = malloc(sizeof *csv); csv->mode = mode; csv->_priv = _easycsv_priv_init(fp, mode, error); if (csv->_priv == NULL) { easycsv_free(csv); return NULL; } return csv; } void easycsv_free(easycsv *csv) { if (csv == NULL) return; _easycsv_priv_free(csv->_priv); free(csv); } _easycsv* _easycsv_priv_init(const char *fp, const EASYCSV_MODE mode, const EASYCSV_ERRORMSG error) { _easycsv *_priv = malloc(sizeof *_priv); memset(_priv, 0, sizeof(*_priv)); // set all members to NULL _priv->error = error; /* Open file according to mode */ switch (mode) { case EASYCSV_R: case EASYCSV_W: { _priv->file = fopen(fp, "r"); break; } default: { _easycsv_printerror(_priv, EASYCSV_UNKNOWNMODE); _easycsv_priv_free(_priv); return NULL; } if (_priv->file == NULL) { _easycsv_printerror(_priv, EASYCSV_OPENFAIL); _easycsv_priv_free(_priv); return NULL; } size_t stfp = strlen(fp); /* Allocate memory for char* */ _priv->fp = malloc(stfp + 1); // + 1 for null strncpy(_priv->fp, fp, stfp); /* Calculate rows and cols if file exists */ if (access(_priv->fp, F_OK) == 0) { _priv->rows = _easycsv_rows(_priv, mode); _priv->cols = _easycsv_columns(_priv, mode); } else { _priv->rows = 0; _priv->cols = 0; } if (mode == EASYCSV_W) { /* csv->tmpfp = malloc(16 + stfp + 1); */ /* strncpy(csv->tmpfp, prefix, 16); */ /* strncat(csv->tmpfp, fp, stfp); */ do { /* Write to temporary file */ unsigned int i = 1; char buffer[21] = "/tmp/easycsv-"; // 13 char, 3 for digits, 4 for .csv, 1 for NULL char *pbuffer = &buffer[13]; // don't free, is used for digit con if (i < 100) { strncpy(pbuffer, "0", 1); pbuffer++; } if (i < 10) { strncpy(pbuffer, "0", 1); pbuffer++; } sprintf(pbuffer, "%i", i); strncpy(++pbuffer, ".csv", 4); buffer[20] = '\0'; if (access(buffer, F_OK) < 0) { i++; } else { _priv->tmpfp = malloc(21); strncpy(_priv->tmpfp, buffer, 21); break; } } while (1); _priv->temp = fopen(_priv->tmpfp, "w"); if (_priv->temp == NULL) { _easycsv_printerror(_priv, EASYCSV_OPENFAIL); _easycsv_priv_free(_priv); return NULL; } /* #ifdef EASYCSV_DEBUG printf("[%i] easycsv_debug: temp file %s opened\n", clock() - csv->start, csv->tmpfp); #endif*/ /* if (freopen(csv->fp, csv->mode, csv->file) == NULL) { fprintf(stderr, "easycsv: failed to set temporary file %s to %s mode\n", csv->tmpfp, csv->mode); return NULL; } if (freopen(csv->tmpfp, csv->mode, csv->temp) == NULL) { fprintf(stderr, "easycsv: failed to set temporary file %s to %s mode\n", csv->tmpfp, csv->mode); return NULL; } */ } return _priv; } void _easycsv_priv_free(_easycsv *_priv) { if (_priv == NULL) return; if (_priv->file != NULL) fclose(_priv->file); if (_priv->temp != NULL) fclose(_priv->temp); if (_priv->fp != NULL) free(_priv->fp); if (_priv->tmpfp != NULL) free(_priv->tmpfp); free(_priv); } int easycsv_pushcolumn(easycsv *csv, const char *col) { if (_easycsv_checkcsvandstring_one(csv, col) < 0) return -1; if (csv->mode == EASYCSV_R) { _easycsv_printerror(csv->_priv, EASYCSV_UNWRITABLE); return -1; } if ((csv->mode == EASYCSV_W) && (access(csv->_priv->fp, F_OK) == 0)) { /* If the file doesn't exist, just put the col in the file */ if (fputs(col, csv->_priv->file) < 0) { _easycsv_printerror(csv->_priv, EASYCSV_PUSHCOLFAIL); return -1; } return 0; } char *str = _easycsv_getrow(csv, 1); size_t ststr = strlen(str); size_t stcol = strlen(col); realloc(str, ststr + stcol + 2); // 1 for null and 1 for comma strncat(str + ststr, ",", 1); strncat(str + ststr + 1, col, stcol); /* #ifdef EASYCSV_DEBUG printf("[%i] easycsv_debug: push column str: %s", clock() - csv->_priv->start, str); #endif */ /* Put str in temp file */ if (fputs(str, csv->_priv->temp) < 0) { _easycsv_printerror(csv->_priv, EASYCSV_PUSHCOLFAIL); return -1; } free(str); /* Copy every row following the first into temp */ for (int i = 2; i <= csv->_priv->rows; i++) { char *row = _easycsv_getrow(csv, i); fputs(row, csv->_priv->temp); free(row); } /* Update the file */ if (_easycsv_update(csv) < 0) { _easycsv_printerror(csv->_priv, EASYCSV_UPDATEFAIL); return -1; } return 0; } int easycsv_pushcolumnvalue(const easycsv *csv, const char *col, const char *val) { if (_easycsv_checkcsvandstring_two(csv, col, val) < 0) return -1; int colnum = _easycsv_getcolumn(csv, col); if (colnum < 0) { _easycsv_printerror(csv->_priv, EASYCSV_COLNUMFAIL); return -1; } /* Find a free cell under col within csv->_priv->cols limits, if there is none, generate a new row */ unsigned int row; for (row = 2; row <= csv->_priv->cols; row++) { if (easycsv_readvalue(csv, colnum, row) != NULL) break; } /* All rows are filled, generate new row */ if (row == csv->_priv->cols) { row++; /* Set file pointer to end */ if (fseek(csv->_priv->file, 0L, SEEK_END) < 0) { _easycsv_printerror(csv->_priv, EASYCSV_FILEPTRFAIL); return -1; } char *rowstr = malloc(csv->_priv->cols + strlen(val) + 2); // 1 extra for '\n' strncpy(rowstr, "\n", 1); for (unsigned int i = 1; i < colnum; i++) { strncpy(rowstr, ",", 1); } strncpy(rowstr, val, strlen(val)); unsigned int colsleft = csv->_priv->cols - colnum; for (unsigned i = 1; i < colsleft; i++) { strncpy(rowstr, ",", 1); } strncpy(rowstr, "/0", 1); csv->_priv->rows++; } /* Fill in rows before */ for (unsigned int i = 1; i < row; i++) { char *str = _easycsv_getrow(csv, i); if (fputs(str, csv->_priv->temp) == EOF) { fprintf(stderr, "easycsv: failed to write \"%s\" in temporary file %s for %s\n", str, csv->_priv->tmpfp, csv->_priv->fp); return -1; } free(str); } return 0; } int easycsv_insertcolumnvalue(const easycsv *csv, const char *col, const unsigned int row, const char *val, const EASYCSV_VALMODE valmode) { if (_easycsv_checkcsvandstring_two(csv, col, val) < 0) return -1; int colnum = _easycsv_getcolumn(csv, col); if (colnum < 0) { _easycsv_printerror(csv->_priv, EASYCSV_COLNUMFAIL); return -1; } char *rowstr = _easycsv_getrow(csv, row); char *pch = rowstr; for (unsigned int i = 1; i < colnum; i++) { pch = strchr(pch, ','); pch++; } size_t st = strcspn(pch, ','); /* Empty cell */ if (st == 0) { } return 0; } int easycsv_printrows(const easycsv *csv) { return csv->_priv->rows; } int easycsv_printcolumns(const easycsv *csv) { return csv->_priv->cols; } int _easycsv_rows(_easycsv *_priv, const EASYCSV_MODE mode) { // no need to check _priv for NULL if (_easycsv_rewind(_priv, mode) < 0) return -1; int rows = 1; char c; /* Go through each character in the file and count the number of \n in it */ while ((c = fgetc(_priv->file)) != EOF) { if (c == '\n') rows++; } return rows; } int _easycsv_columns(_easycsv *_priv, const EASYCSV_MODE mode) { // no need to check _priv for NULL /* Prepare it for reading */ if (_easycsv_rewind(_priv, mode) < 0) return -1; /* 1. check if empty file 2. check if only one column -> 0 commas 3. if >1 column, n commas = n+1 columns */ unsigned int col = 1; char c; while ((c = fgetc(_priv->file)) != '\n') { if (c == ',') col++; } return col; } const char* easycsv_readcolumnvalue(const easycsv *csv, const char *col, const unsigned int row) { if (_easycsv_checkcsvandstring_one(csv, col) < 0) return NULL; if (row > csv->_priv->rows) { _easycsv_printerror(csv->_priv, EASYCSV_OVERMAXROW); return NULL; } int i = _easycsv_getcolumn(csv, col); if (i < 1) { _easycsv_printerror(csv->_priv, EASYCSV_COLNUMFAIL); return NULL; } return easycsv_readvalue(csv, i, row); } const char* easycsv_readvalue(const easycsv *csv, const unsigned int col, const unsigned int row) { if (row == 0) { _easycsv_printerror(csv->_priv, EASYCSV_ZEROROW); return NULL; } if (col == 0) { _easycsv_printerror(csv->_priv, EASYCSV_ZEROCOL); return NULL; } char *str = _easycsv_getrow(csv, row); if (str == NULL) { _easycsv_printerror(csv->_priv, EASYCSV_ROWNOTEXIST); return NULL; } /* #ifdef EASYCSV_DEBUG printf("[%i] rv_col: %i\nstr: %s\n", clock() - csv->_priv->start, col, str); #endif */ /* Return if first col */ if (col == 1) { size_t st = strcspn(str, ","); if (st == 0) { free(str); return NULL; } char *pch = malloc(st); strncpy(pch, str, st); free(str); return pch; } /* Get first occurance of comma in str, the first value is ommited but not the comma */ char *pch = strpbrk(str, ","); /* Repeat until desired col is found */ for (int i = 2; i < col; i++) { pch = strpbrk(pch + 1, ","); } /* Get span from start of string to first occurence of comma */ size_t st = strcspn(pch + 1, ","); /* If 0, no string exists! */ if (st == 0) { free(str); return NULL; } char *val = malloc(st + 1); strncpy(val, pch + 1, st); strncat(val, "\0", 1); free(str); return val; } /* PRIVATE FUNCTIONS */ /* static int _easycsv_checkmode(struct easycsv *csv, const char mode) { if (strchr(csv->mode, mode) == NULL) { return -1; } } */ int _easycsv_update(easycsv *csv) { if (csv == NULL) { _easycsv_printerror(csv->_priv, EASYCSV_NULLCSV); return -1; } /* Set temp file to read binary */ if (freopen(csv->_priv->tmpfp, "rb", csv->_priv->temp) == NULL) { _easycsv_printerror(csv->_priv, EASYCSV_REOPENFAIL); easycsv_free(csv); return -1; } /* Set file to write binary */ if (freopen(csv->_priv->fp, "wb", csv->_priv->file) == NULL) { _easycsv_printerror(csv->_priv, EASYCSV_REOPENFAIL); easycsv_free(csv); return -1; } char buf[BUFSIZ]; size_t size; while (size = fread(buf, 1, BUFSIZ, csv->_priv->temp)) { fwrite(buf, 1, size, csv->_priv->file); } /* Set temp file back to write */ if (freopen(csv->_priv->tmpfp, "w", csv->_priv->temp) == NULL) { _easycsv_printerror(csv->_priv, EASYCSV_REOPENFAIL); easycsv_free(csv); return -1; } /* Set file back to read */ if (freopen(csv->_priv->fp, "r", csv->_priv->file) == NULL) { _easycsv_printerror(csv->_priv, EASYCSV_REOPENFAIL); easycsv_free(csv); return -1; } return 0; } char* _easycsv_getrow(const easycsv *csv, const unsigned int row) { if (csv == NULL) { _easycsv_printerror(csv->_priv, EASYCSV_NULLCSV); return NULL; } if (row > csv->_priv->rows) { _easycsv_printerror(csv->_priv, EASYCSV_OVERMAXROW); return NULL; } if (row == 0) { _easycsv_printerror(csv->_priv, EASYCSV_ZEROROW); return NULL; } /* Allocate memory */ char *str = malloc(BUFSIZ); /* Set file pointer to start */ rewind(csv->_priv->file); for (int i = 1; i < row; i++) { /* skip until row is reached */ fscanf(csv->_priv->file, "%*[^\n]\n", NULL); } /* Grab the row and store it in str */ fscanf(csv->_priv->file, "%s\n", str); // printf("row: %s\n", str); return str; } int _easycsv_rewind(_easycsv *_priv, const EASYCSV_MODE mode) { /* Check if empty file */ if (fscanf(_priv->file, "\n") == EOF) { _easycsv_printerror(_priv, EASYCSV_EMPTYCSV); return -1; } /* Check if file is readable */ if (mode != EASYCSV_R) { _easycsv_printerror(_priv, EASYCSV_UNREADABLE); return -1; } /* Set file pointer to the start */ rewind(_priv->file); return 0; } int _easycsv_getcolumn(const easycsv *csv, const char *col) { if (_easycsv_checkcsvandstring_one(csv, col) < 0) return -1; /* Grab str of row 1 */ char *firstrow = _easycsv_getrow(csv, 1); if (firstrow == NULL) { _easycsv_printerror(csv->_priv, EASYCSV_ROWNOTEXIST); return -1; } unsigned int commas = 0; // printf("FIRST COLUMN: %s\n", firstrow); /* Find first occurance of col in firstrow */ char *str = strstr(firstrow, col); if (str == NULL) { _easycsv_printerror(csv->_priv, EASYCSV_COLNOTEXIST); return -1; } /* Count numbers of commas following str */ char *c = strpbrk(str, ","); while (c != NULL) { commas++; c = strpbrk(c + 1, ","); } /* no need to free c as it is already NULL at this point */ // free((char*) str); apparently invalid pointer // printf("ROW: %i\nCOL: %i\n", row, csv->_priv->cols - commas); /* #ifdef EASYCSV_DEBUG printf("[%i] rcv_commas: %i\n", clock() - csv->_priv->start, commas); #endif */ free(firstrow); return csv->_priv->cols - commas; } /* int _easycsv_checkifvalue(struct easycsv *csv, const int col, const int row) { const char *rowstr = _easycsv_getrow(csv, row); return 0; } char *_easycsv_getvalueinrow(const int col, const char *row) { char *pbrk = strchr(row, ','); for (unsigned int i = 1; i < col; i++) { } } */ void _easycsv_printerror(const _easycsv *_priv, const EASYCSV_ERROR error) { switch (_priv->error) { case EASYCSV_ERROROFF: return; case EASYCSV_ERRORSTDOUT: _easycsv_geterror(_priv, error, stdout); break; case EASYCSV_ERRORSTDERR: default: _easycsv_geterror(_priv, error, stderr); return; } } void _easycsv_geterror(const _easycsv *_priv, const EASYCSV_ERROR error, FILE *fs) { #ifdef EASYCSV_DEBUG fprintf(stderr, "[%i] ", clock() - _priv->start); #endif fputs("easycsv: ", fs); switch (error) { case EASYCSV_NOERROR: fputs("no error", fs); break; case EASYCSV_NULLCSV: fputs("easycsv pointer is NULL", fs); break; case EASYCSV_NULLPTR: fputs("pointer is NULL", fs); break; case EASYCSV_EMPTYSTRING: fputs("string is empty", fs); break; case EASYCSV_OVERMAXROW: fprintf(fs, "int exceeds row limit %i", _priv->rows); break; case EASYCSV_OVERMAXCOL: fprintf(fs, "int exceeds column limit %i", _priv->cols); break; case EASYCSV_ZEROROW: fputs("parameterised row number is zero", fs); break; case EASYCSV_ZEROCOL: fputs("parameterised column number is zero", fs); break; case EASYCSV_UNKNOWNMODE: fputs("unknown file IO mode", fs); break; case EASYCSV_OPENFAIL: fputs("failed to open file", fs); break; case EASYCSV_REOPENFAIL: fputs("failed to reopen file", fs); break; case EASYCSV_EMPTYCSV: fputs("CSV file is empty", fs); break; case EASYCSV_UNWRITABLE: fputs("CSV file is not in a writable mode", fs); break; case EASYCSV_UNREADABLE: fputs("CSV file is not in a readable mode", fs); break; case EASYCSV_UPDATEFAIL: fputs("CSV file has failed to update", fs); break; case EASYCSV_FILEPTRFAIL: fputs("failed to move FILE pointer", fs); break; case EASYCSV_ROWNOTEXIST: fputs("given row does not exist", fs); break; case EASYCSV_COLNOTEXIST: fputs("given column does not exist", fs); break; case EASYCSV_PUSHCOLFAIL: fputs("failed to push value under column", fs); break; case EASYCSV_COLNUMFAIL: fputs("failed to determine the column number of a value in the first row", fs); break; default: fputs("unknown error, easycsv* is possibly NULL", fs); break; } if (_priv->fp != NULL) { fprintf(fs, " in %s", _priv->fp); } fputc('\n', fs); } int _easycsv_checkcsvandstring_one(const easycsv *csv, const char *one) { if (csv == NULL) { _easycsv_printerror(csv->_priv, EASYCSV_NULLCSV); return -1; } if ((one == NULL) || (one[0] == '\0')) { _easycsv_printerror(csv->_priv, EASYCSV_EMPTYSTRING); return -1; } return 0; } int _easycsv_checkcsvandstringtwo(const easycsv *csv, const char *one, const char *two) { if (_easycsv_checkcsvandstring_one(csv, one) < 0) return -1; if ((two == NULL) || (two[0] == '\0')) { _easycsv_printerror(csv->_priv, EASYCSV_EMPTYSTRING); return -1; } return 0; }