#include "_easycsv.h" /* (Constructor) Initialise _easycsv */ _easycsv* _easycsv_priv_init(const char *fp, const EASYCSV_MODE mode, const EASYCSV_ERRORMSG error) { _easycsv *_priv = malloc(sizeof(_easycsv)); _priv->file = NULL; _priv->temp = NULL; _priv->fp = NULL; _priv->tmpfp = NULL; _priv->rows = 0; _priv->cols = 0; #ifdef EASYCSV_DEBUG _priv->start = 0; #endif _priv->error = error; /* Open file according to mode */ switch (mode) { case EASYCSV_R: { _priv->file = fopen(fp, "r"); break; } case EASYCSV_W: { _priv->file = fopen(fp, "w"); break; } default: { _easycsv_printerror(_priv, EASYCSV_UNKNOWNIOMODE); _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 strcpy(_priv->fp, fp); /* 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); } 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 if (i < 100) strncat(buffer, "0", 1); if (i < 10) strncat(buffer, "0", 1); sprintf(buffer + strlen(buffer), "%i", i); strcat(buffer, ".csv"); 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; } /* (Destructor) Free _easycsv memory */ void _easycsv_priv_free(_easycsv *_priv) { /* ARGS CHECK */ if (_priv == NULL) return; /* ARGS CHECK */ 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_update(_easycsv *csv) { /* ARGS CHECK */ if (csv == NULL) { _easycsv_printerror(csv, EASYCSV_NULLCSV); return -1; } /* ARGS CHECK */ /* Set temp file to read binary */ if (freopen(csv->tmpfp, "rb", csv->temp) == NULL) { _easycsv_printerror(csv, EASYCSV_REOPENFAIL); easycsv_free(csv); return -1; } /* Set file to write binary */ if (freopen(csv->fp, "wb", csv->file) == NULL) { _easycsv_printerror(csv, EASYCSV_REOPENFAIL); easycsv_free(csv); return -1; } /* Copy entire file */ char buf[BUFSIZ]; size_t size; while (size = fread(buf, 1, BUFSIZ, csv->temp)) { fwrite(buf, 1, size, csv->file); } /* Set temp file back to write */ if (freopen(csv->tmpfp, "w", csv->temp) == NULL) { _easycsv_printerror(csv, EASYCSV_REOPENFAIL); easycsv_free(csv); return -1; } /* Set file back to read */ if (freopen(csv->fp, "r", csv->file) == NULL) { _easycsv_printerror(csv, EASYCSV_REOPENFAIL); easycsv_free(csv); return -1; } return 0; } 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 */ int col = 1; char c; while ((c = fgetc(_priv->file)) != '\n') { if (c == ',') col++; } return col; } char* _easycsv_getrow(const _easycsv *csv, const unsigned int row) { /* ARGS CHECK */ if (csv == NULL) { _easycsv_printerror(csv, EASYCSV_NULLCSV); return NULL; } if (row > csv->rows) { _easycsv_printerror(csv, EASYCSV_OVERMAXROW); return NULL; } if (row == 0) { _easycsv_printerror(csv, EASYCSV_ZEROROW); return NULL; } /* END ARGS CHECK */ /* Allocate memory */ char *str = malloc(BUFSIZ); /* Set file pointer to start */ rewind(csv->file); for (int i = 1; i < row; i++) { /* skip until row is reached */ fscanf(csv->file, "%*[^\n]\n", NULL); } /* Grab the row and store it in str */ fscanf(csv->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) { /* ARGS CHECK */ if (_easycsv_checkcsvandstring_one(csv, col) < 0) return -1; /* ARGS CHECK */ /* Grab str of row 1 */ char *firstrow = _easycsv_getrow(csv, 1); if (firstrow == NULL) { _easycsv_printerror(csv, 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, 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->cols - commas); /* #ifdef EASYCSV_DEBUG printf("[%i] rcv_commas: %i\n", clock() - csv->start, commas); #endif */ free(firstrow); return csv->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 _easycsv *_priv, const char *row, const unsigned int col) { size_t st; char *pch = NULL; /* If not first column */ if (col != 1) { /* Get first occurance of comma in str, the first value is ommited but not the comma */ char *pch = strpbrk(row, ","); /* 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 */ st = strcspn(pch + 1, ","); /* If 0, no string exists! */ if (st == 0) { _easycsv_printerror(_priv, EASYCSV_EMPTYVALUE); return NULL; } char *val = malloc(st + 1); strncpy(val, pch + 1, st); strncat(val, "\0", 1); return val; } char* _easycsv_setcharptovalue(const _easycsv *_priv, const char *rowstr, const unsigned int col) { char *pch = rowstr; for (unsigned int i = 1; i < col; i++) { pch = strchr(rowstr, ','); if (pch == NULL) { _easycsv_printerror(_priv, EASYCSV_NULLPTR); return NULL; } pch++; } return pch; } 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) { /* Generic errors */ 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_EMPTYVALUE: fputs("value in CSV file 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; /* File input/output errors */ case EASYCSV_UNKNOWNIOMODE: 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_UPDATETEMPFAIL: fputs("failed to update temp CSV file", fs); break; case EASYCSV_FILEPTRFAIL: fputs("failed to move FILE pointer", fs); break; /* Non-existant elements */ case EASYCSV_ROWNOTEXIST: fputs("given row does not exist", fs); break; case EASYCSV_COLNOTEXIST: fputs("given column does not exist", fs); break; /* User-facing failure */ 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, EASYCSV_NULLCSV); return -1; } if (one == NULL) { _easycsv_printerror(csv, EASYCSV_EMPTYSTRING); return -1; } return 0; } int _easycsv_checkcsvandstring_two(const _easycsv *csv, const char *one, const char *two) { if (_easycsv_checkcsvandstring_one(csv, one) < 0) return -1; if (two == NULL) { _easycsv_printerror(csv, EASYCSV_EMPTYSTRING); return -1; } return 0; }