From a143d3db93247a5cc82414d26280f03af4a0bf65 Mon Sep 17 00:00:00 2001 From: Pradana Aumars Date: Wed, 21 Feb 2018 20:40:51 +0100 Subject: [PATCH] Added more features --- src/easycsv.c | 1050 +++++++++++++++++++++++++------------- src/easycsv.h | 189 +++++-- tests/01 | Bin 13456 -> 0 bytes tests/{ => 01}/01.c | 0 tests/{csv => 01}/01.csv | 0 tests/{ => 02}/02.c | 0 6 files changed, 834 insertions(+), 405 deletions(-) delete mode 100755 tests/01 rename tests/{ => 01}/01.c (100%) rename tests/{csv => 01}/01.csv (100%) rename tests/{ => 02}/02.c (100%) diff --git a/src/easycsv.c b/src/easycsv.c index ab55f70..9079b2d 100644 --- a/src/easycsv.c +++ b/src/easycsv.c @@ -1,44 +1,59 @@ #include "easycsv.h" -/* Flags */ +/* Generic type definitions for internal use */ + +typedef uint32_t row_t; // max length of 2^32 - 1 +typedef uint32_t column_t; +typedef char* rowstr; // non-const, to free it after usage + +/* I may possibly need to make types for rows, + iterators and columns */ + +/* Flags denoting cell type */ typedef enum { EASYCSV_NONE, EASYCSV_STRING, EASYCSV_INT, - EASYCSV_UNKNOWN + EASYCSV_FLOAT, + EASYCSV_UNKNOWNTYPE } _EASYCSV_TYPE; +/* Error flags */ typedef enum { /* Generic errors */ - EASYCSV_NOERROR = 0, // no error + EASYCSV_NOERROR, // no error EASYCSV_NULLCSV, // easycsv* is NULL EASYCSV_NULLPTR, // generic pointer is NULL EASYCSV_EMPTYSTRING, // empty string + EASYCSV_EMPTYVALUE, // value is empty 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_UNKNOWNIOMODE, // 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_UPDATEFAIL, // update file fail = temp -> file + EASYCSV_UPDATETEMPFAIL, // update = -> temp EASYCSV_FILEPTRFAIL, // move file pointer fail /* Non-existant elements */ EASYCSV_ROWNOTEXIST, // row does not exist EASYCSV_COLNOTEXIST, // column does not exist - /* Subroutine failure */ + /* User-facing failure */ EASYCSV_PUSHCOLFAIL, // push column value fail - EASYCSV_COLNUMFAIL // column number retrieval fail + EASYCSV_COLNUMFAIL, // column number retrieval fail + EASYCSV_READVALUEFAIL, // read value fail } EASYCSV_ERROR; +/* Private easycsv members */ typedef struct _easycsv { FILE *file; // original CSV file FILE *temp; // temporary CSV file for writing @@ -54,52 +69,100 @@ typedef struct _easycsv { /* PRIVATE FUNCTIONS */ -/* Constructor of private members */ -static _easycsv *_easycsv_priv_init(const char*, const EASYCSV_MODE, const EASYCSV_ERRORMSG); +/* (Constructor) Initialise _easycsv */ +static _easycsv* +_easycsv_priv_init(const char*, + const EASYCSV_MODE, + const EASYCSV_ERRORMSG); -/* Destructor of private members */ -static void _easycsv_priv_free(_easycsv*); +/* (Destructor) Free _easycsv memory */ +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*); +static int +_easycsv_update(easycsv*); /* Rewind easycsv, checks for readability */ -static int _easycsv_rewind(_easycsv*, const EASYCSV_MODE); +static int +_easycsv_rewind(_easycsv*, + const EASYCSV_MODE); -/* Returns string of a specific row */ -static char *_easycsv_getrow(const easycsv*, const unsigned int); +/* Returns string of a specific row, + includes the '\n' character as well! */ +static char* +_easycsv_getrow(const easycsv*, + const unsigned int); /* Return column number of a named column */ -static int _easycsv_getcolumn(const easycsv*, const char*); +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 the type of the value (eg: string or int) */ +static _EASYCSV_TYPE +_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 int +_easycsv_checkifvalue(const easycsv*, + const int, + const int); -/* */ -// static char *_easycsv_getvalueinrow(const int, const char*); +/* Grab const char* in row */ +static char* +_easycsv_getvalueinrow(const _easycsv*, + const char*, + const unsigned int); + +/* Returns char pointer to start of value in rowstr */ +static char* +_easycsv_setcharptovalue(const _easycsv*, + const char*, + const unsigned int); + +/* Insert value in row in specific column */ +static char* +_easycsv_insertvalueinrow(const _easycsv*, + const char*, + const char*, + const unsigned int); /* Calculate rows */ -static int _easycsv_rows(_easycsv*, const EASYCSV_MODE); +static int +_easycsv_rows(_easycsv*, + const EASYCSV_MODE); /* Calculate columns*/ -static int _easycsv_columns(_easycsv*, const EASYCSV_MODE); +static int +_easycsv_columns(_easycsv*, + const EASYCSV_MODE); -static void _easycsv_printerror(const _easycsv*, const EASYCSV_ERROR); +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*); +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*); +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*); +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*); @@ -116,16 +179,23 @@ static int _easycsv_checkcsvandstring_two(const easycsv*, const char*, const cha * ==================== */ -/* PUBLIC FUNCTIONS */ +/* == PUBLIC FUNCTIONS == */ +/* CONSTRUCTORS AND DESTRUCTORS */ + +/* (Constructor) Initialise easycsv */ easycsv* -easycsv_init(const char *fp, const EASYCSV_MODE mode) +easycsv_init(const char *fp, + const EASYCSV_MODE mode) { return easycsv_init_errormsg(fp, mode, EASYCSV_ERRORSTDERR); } +/* (Constructor) Initialise easycsv with error message option */ easycsv* -easycsv_init_errormsg(const char *fp, const EASYCSV_MODE mode, const EASYCSV_ERRORMSG error) +easycsv_init_errormsg(const char *fp, + const EASYCSV_MODE mode, + const EASYCSV_ERRORMSG error) { #ifdef EASYCSV_DEBUG csv->start = clock(); @@ -143,18 +213,442 @@ easycsv_init_errormsg(const char *fp, const EASYCSV_MODE mode, const EASYCSV_ERR return csv; } +/* (Destructor) Free easycsv memory */ void easycsv_free(easycsv *csv) { - if (csv == NULL) return; + /* ARGS CHECK */ + if (csv == NULL) + return; + /* END ARGS CHECK */ + _easycsv_priv_free(csv->_priv); free(csv); } +/* GENERIC ALGORITHMS */ + +/* READ VALUE */ +char* +easycsv_readvalue(const easycsv *csv, + const unsigned int col, + const unsigned int row) +{ + /* ARGS CHECK */ + if (row == 0) { + _easycsv_printerror(csv->_priv, EASYCSV_ZEROROW); + _easycsv_printerror(csv->_priv, EASYCSV_READVALUEFAIL); + return NULL; + } + + if (col == 0) { + _easycsv_printerror(csv->_priv, EASYCSV_ZEROCOL); + _easycsv_printerror(csv->_priv, EASYCSV_READVALUEFAIL); + return NULL; + } + /* END ARGS CHECK */ + + char *str = _easycsv_getrow(csv, row); + if (str == NULL) { + _easycsv_printerror(csv->_priv, EASYCSV_ROWNOTEXIST); + _easycsv_printerror(csv->_priv, EASYCSV_READVALUEFAIL); + return NULL; + } + + char *val = _easycsv_getvalueinrow(csv->_priv, str, col); + if (val == NULL) { + _easycsv_printerror(csv->_priv, EASYCSV_READVALUEFAIL); + return NULL; + } + return val; +} + +char* +easycsv_readcolumnvalue(const easycsv *csv, + const char *col, + const unsigned int row) +{ + /* ARGS CHECK */ + if (_easycsv_checkcsvandstring_one(csv, col) < 0) + return NULL; + + if (row > csv->_priv->rows) { + _easycsv_printerror(csv->_priv, EASYCSV_OVERMAXROW); + return NULL; + } + /* END ARGS CHECK */ + + int i = _easycsv_getcolumn(csv, col); + if (i < 1) { + _easycsv_printerror(csv->_priv, EASYCSV_COLNUMFAIL); + return NULL; + } + + return easycsv_readvalue(csv, i, row); +} + +int +easycsv_printrows(const easycsv *csv) +{ + return csv->_priv->rows; +} + +int +easycsv_printcolumns(const easycsv *csv) +{ + return csv->_priv->cols; +} + +/* INSERT VALUE -- AT SPECIFIC ROW AND COLUMN */ + +int +easycsv_insertvalue(easycsv *csv, + const char *val, + const unsigned int col, + const unsigned int row) +{ + return easycsv_insertvaluemode(csv, val, col, row, EASYCSV_REPLACE); +} + +int +easycsv_insertvaluemode(easycsv *csv, + const char *val, + const unsigned int col, + const unsigned int row, + const EASYCSV_VALMODE valmode) +{ + /* ARGS CHECK */ + if (_easycsv_checkcsvandstring_one(csv, val) < 0) + return -1; + + if (col == 0) { + _easycsv_printerror(csv->_priv, EASYCSV_ZEROCOL); + return -1; + } + + if (row == 0) { + _easycsv_printerror(csv->_priv, EASYCSV_ZEROROW); + return -1; + } + /* END ARGS CHECK */ + + /* row extends max */ + if (row > csv->_priv->rows) { + } + + char *rowstr = _easycsv_getrow(csv, row); + if (rowstr == NULL) + return -1; + + size_t rowstrst = strlen(rowstr); + size_t st = 0; + size_t commas = 0; + char *pch = NULL; + char *newstr = NULL; + + /* column is within limit */ + if (col <= csv->_priv->cols) { + + /* Set pch to start of value in rowstr */ + pch = _easycsv_setcharptovalue(csv->_priv, rowstr, col); + if (pch == NULL) + return -1; + + /* Calculate size of existing value */ + st = strcspn(pch, ","); + + newstr = malloc(rowstrst - st + strlen(val) + 1); + + /* Copy char to newstr before value (pch) */ + strncpy(newstr, rowstr, pch - rowstr); + + /* Insert value */ + if (st != 0) { /* Occupied cell */ + switch (valmode) { + case EASYCSV_CONCAT: { + strncat(newstr, pch, st); + strcat(newstr, val); + break; + } + case EASYCSV_RCONCAT: { + strcat(newstr, val); + strncat(newstr, pch, st); + break; + } + case EASYCSV_REPLACE: + default: { + strcat(newstr, val); + break; + } + } + } + else { /* Empty cell */ + strcat(newstr, val); + } + + /* Set pch to after value */ + pch = strchr(rowstr, ','); + + /* Calculate length of rest of string */ + st = strlen(pch); + + /* Concentate rest of string including NULL char */ + strcat(newstr, pch); + } + else { + commas = col - csv->_priv->cols; + csv->_priv->cols = col; + newstr = malloc(rowstrst + commas + strlen(val) + 1); + strncpy(newstr, rowstr, rowstrst); + for (size_t i = 0; i < commas; i++) + strncat(newstr, ",", 1); + strcat(newstr, val); // append \0 + } + + /* UPDATE CSV */ + + char *str = NULL; + + /* Row within limits */ + if (row <= csv->_priv->rows) { + + /* Copy rows before newstr in csv to temp */ + for (unsigned int i = 1; i < row; i++) { + str = _easycsv_getrow(csv, i); + if (col > csv->_priv->cols) { + str = realloc(str, strlen(str) + commas + 1); + for (size_t j = 0; j < commas; j++) { + strncat(str, ",", 1); + } + } + fputs(str, csv->_priv->temp); + + free(str); + } + + /* Print newstr into temp */ + fputs(newstr, csv->_priv->temp); + + /* Copy the rest of rows */ + for (unsigned int i = row + 1; i <= csv->_priv->rows; i++) { + str = _easycsv_getrow(csv, i); + if (col > csv->_priv->cols) { + str = realloc(str, strlen(str) + commas + 1); + for (size_t j = 0; j < commas; j++) { + strncat(str, ",", 1); + } + } + fputs(str, csv->_priv->temp); + + free(str); + } + } + else { /* Row exceeds limit */ + + /* Copy entire file */ + char buf[BUFSIZ]; + size_t size; + while (size = fread(buf, 1, BUFSIZ, csv->_priv->file)) { + fwrite(buf, 1, size, csv->_priv->temp); + } + + /* Print out commas on rows before newstr */ + for (size_t i = csv->_priv->rows; i < row; i++) { + for (size_t j = 0; j < csv->_priv->cols; j++) + fputc(',', csv->_priv->temp); + fputc('\n', csv->_priv->temp); + } + + fputs(newstr, csv->_priv->temp); + } + + /* Update csv */ + if (_easycsv_update(csv) < 0) + return -1; + + /* END UPDATE CSV */ + + free(rowstr); // including pch + free(newstr); + + return 0; +} + +int +easycsv_insertcolumnvalue(easycsv *csv, + const char *col, + const unsigned int row, + const char *val) +{ + return easycsv_insertcolumnvaluemode(csv, col, row, val, EASYCSV_REPLACE); +} + +int +easycsv_insertcolumnvaluemode(easycsv *csv, + const char *col, + const unsigned int row, + const char *val, + const EASYCSV_VALMODE valmode) +{ + /* ARGS CHECK */ + if (_easycsv_checkcsvandstring_two(csv, col, val) < 0) + return -1; + + if (row == 0) { + _easycsv_printerror(csv->_priv, EASYCSV_ZEROROW); + return -1; + } + /* END ARGS CHECK */ + + int colnum = _easycsv_getcolumn(csv, col); + if (colnum < 0) { + _easycsv_printerror(csv->_priv, EASYCSV_COLNUMFAIL); + return -1; + } + + return easycsv_insertvaluemode(csv, val, colnum, row, valmode); +} + +/* PUSH VALUE */ + +int +easycsv_pushcolumn(easycsv *csv, + const char *col) +{ + /* ARGS CHECK */ + if (_easycsv_checkcsvandstring_one(csv, col) < 0) + return -1; + /* END ARGS CHECK */ + + 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; + } + + /* Grab first row */ + char *str = _easycsv_getrow(csv, 1); + char *pch = NULL; + size_t i; + + /* Find empty column in first row */ + for (i = 1; i < csv->_priv->cols; i++) { + if (strcspn(pch, ",") == 0) + break; + pch = strchr(str, ','); + pch++; + } + + /* No empty columns in first row */ + if (i == csv->_priv->cols) + i++; + + return easycsv_insertvalue(csv, col, i, 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); + + // 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); + }*/ +} + +int +easycsv_pushcolumnvalue(easycsv *csv, + const char *col, + const char *val) +{ + /* ARGS CHECK */ + if (_easycsv_checkcsvandstring_two(csv, col, val) < 0) + return -1; + /* ARGS CHECK */ + + 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); // 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++; + } + /* ROW WILL NOT BE GENERATED \\ row < csv->_priv->rows */ + + return easycsv_insertvalue(csv, val, colnum, row); +} + +/* PRIVATE FUNCTIONS */ + +/* (Constructor) Initialise _easycsv */ _easycsv* -_easycsv_priv_init(const char *fp, const EASYCSV_MODE mode, const EASYCSV_ERRORMSG error) +_easycsv_priv_init(const char *fp, + const EASYCSV_MODE mode, + const EASYCSV_ERRORMSG error) { _easycsv *_priv = malloc(sizeof *_priv); + calloc(_priv, sizeof(*_priv)); memset(_priv, 0, sizeof(*_priv)); // set all members to NULL _priv->error = error; @@ -167,7 +661,7 @@ _easycsv_priv_init(const char *fp, const EASYCSV_MODE mode, const EASYCSV_ERRORM break; } default: { - _easycsv_printerror(_priv, EASYCSV_UNKNOWNMODE); + _easycsv_printerror(_priv, EASYCSV_UNKNOWNIOMODE); _easycsv_priv_free(_priv); return NULL; } @@ -260,326 +754,35 @@ _easycsv_priv_init(const char *fp, const EASYCSV_MODE mode, const EASYCSV_ERRORM return _priv; } - + +/* (Destructor) Free _easycsv memory */ 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); + /* 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_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) { + /* ARGS CHECK */ if (csv == NULL) { _easycsv_printerror(csv->_priv, EASYCSV_NULLCSV); return -1; } + /* ARGS CHECK */ /* Set temp file to read binary */ if (freopen(csv->_priv->tmpfp, "rb", csv->_priv->temp) == NULL) { @@ -594,7 +797,8 @@ _easycsv_update(easycsv *csv) easycsv_free(csv); return -1; } - + + /* Copy entire file */ char buf[BUFSIZ]; size_t size; while (size = fread(buf, 1, BUFSIZ, csv->_priv->temp)) { @@ -618,9 +822,61 @@ _easycsv_update(easycsv *csv) return 0; } -char* -_easycsv_getrow(const easycsv *csv, const unsigned int row) +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; +} + + + + + +char* +_easycsv_getrow(const easycsv *csv, + const unsigned int row) +{ + /* ARGS CHECK */ if (csv == NULL) { _easycsv_printerror(csv->_priv, EASYCSV_NULLCSV); return NULL; @@ -635,6 +891,7 @@ _easycsv_getrow(const easycsv *csv, const unsigned int row) _easycsv_printerror(csv->_priv, EASYCSV_ZEROROW); return NULL; } + /* END ARGS CHECK */ /* Allocate memory */ char *str = malloc(BUFSIZ); @@ -657,7 +914,8 @@ _easycsv_getrow(const easycsv *csv, const unsigned int row) } int -_easycsv_rewind(_easycsv *_priv, const EASYCSV_MODE mode) +_easycsv_rewind(_easycsv *_priv, + const EASYCSV_MODE mode) { /* Check if empty file */ if (fscanf(_priv->file, "\n") == EOF) { @@ -678,9 +936,13 @@ _easycsv_rewind(_easycsv *_priv, const EASYCSV_MODE mode) } int -_easycsv_getcolumn(const easycsv *csv, const char *col) +_easycsv_getcolumn(const easycsv *csv, + const char *col) { - if (_easycsv_checkcsvandstring_one(csv, col) < 0) return -1; + /* ARGS CHECK */ + if (_easycsv_checkcsvandstring_one(csv, col) < 0) + return -1; + /* ARGS CHECK */ /* Grab str of row 1 */ char *firstrow = _easycsv_getrow(csv, 1); @@ -730,18 +992,68 @@ int _easycsv_checkifvalue(struct easycsv *csv, const int col, const int row) return 0; } - -char *_easycsv_getvalueinrow(const int col, const char *row) -{ - char *pbrk = strchr(row, ','); - for (unsigned int i = 1; i < col; i++) { - - } -} */ +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; +} + +static 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) +_easycsv_printerror(const _easycsv *_priv, + const EASYCSV_ERROR error) { switch (_priv->error) { case EASYCSV_ERROROFF: return; @@ -752,7 +1064,9 @@ _easycsv_printerror(const _easycsv *_priv, const EASYCSV_ERROR error) } void -_easycsv_geterror(const _easycsv *_priv, const EASYCSV_ERROR error, FILE *fs) +_easycsv_geterror(const _easycsv *_priv, + const EASYCSV_ERROR error, + FILE *fs) { #ifdef EASYCSV_DEBUG fprintf(stderr, "[%i] ", clock() - _priv->start); @@ -761,41 +1075,48 @@ _easycsv_geterror(const _easycsv *_priv, const EASYCSV_ERROR error, FILE *fs) 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; - - case EASYCSV_UNKNOWNMODE: fputs("unknown file IO mode", 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) { + if (_priv->fp != NULL) fprintf(fs, " in %s", _priv->fp); - } fputc('\n', fs); } int -_easycsv_checkcsvandstring_one(const easycsv *csv, const char *one) +_easycsv_checkcsvandstring_one(const easycsv *csv, + const char *one) { if (csv == NULL) { _easycsv_printerror(csv->_priv, EASYCSV_NULLCSV); @@ -811,9 +1132,12 @@ _easycsv_checkcsvandstring_one(const easycsv *csv, const char *one) } int - _easycsv_checkcsvandstringtwo(const easycsv *csv, const char *one, const char *two) +_easycsv_checkcsvandstringtwo(const easycsv *csv, + const char *one, + const char *two) { - if (_easycsv_checkcsvandstring_one(csv, one) < 0) return -1; + if (_easycsv_checkcsvandstring_one(csv, one) < 0) + return -1; if ((two == NULL) || (two[0] == '\0')) { _easycsv_printerror(csv->_priv, EASYCSV_EMPTYSTRING); diff --git a/src/easycsv.h b/src/easycsv.h index 4b9b2da..f8eb9d8 100644 --- a/src/easycsv.h +++ b/src/easycsv.h @@ -9,30 +9,41 @@ #include #include #include +#include /* Flags for error messages */ typedef enum { + EASYCSV_ERRORSTDERR = 0, // default EASYCSV_ERROROFF, - EASYCSV_ERRORSTDERR, EASYCSV_ERRORSTDOUT } EASYCSV_ERRORMSG; -/* Using our own flags to facilitate changing modes during file copying phases */ +/* Flags to facilitate changing modes during file copying phases */ typedef enum { + EASYCSV_UNKNOWNMODE = 0, EASYCSV_R, EASYCSV_W } EASYCSV_MODE; +/* Flags to parameterise the easycsv_insertvalue */ typedef enum { - EASYCSV_REPLACE, - EASYCSV_APPEND, - EASYCSV_APPEND_R, + EASYCSV_REPLACE, /* (Default) Replaces word */ + EASYCSV_CONCAT, /* Concantates word at the end */ + EASYCSV_RCONCAT, /* Concantates word from the start */ } EASYCSV_VALMODE; +typedef enum { + EASYCSV_ALPHA, /* sort alphabetically */ + EASYCSV_RALPHA, /* sort reverse alphabetically */ + EASYCSV_NUMER, /* sort numerically, from lowest value to highest */ + EASYCSV_RNUMER /* sort from highest value to lowest */ +} EASYCSV_SORT; + typedef struct _easycsv _easycsv; +/* Public easycsv members */ typedef struct easycsv { - EASYCSV_MODE mode; + EASYCSV_MODE mode; // end users able to easily modify mode to their needs _easycsv *_priv; // private members } easycsv; @@ -42,61 +53,155 @@ typedef struct easycsv { * ============================ */ +/* CONSTRUCTORS AND DESTRUCTORS */ + /* (Constructor) Initialise easycsv */ -easycsv* easycsv_init(const char*, const EASYCSV_MODE); +easycsv* +easycsv_init(const char*, + const EASYCSV_MODE); /* (Constructor) Initialise easycsv with error message option */ -easycsv* easycsv_init_errormsg(const char*, const EASYCSV_MODE, const EASYCSV_ERRORMSG); +easycsv* +easycsv_init_errormsg(const char*, + const EASYCSV_MODE, + const EASYCSV_ERRORMSG); /* (Destructor) Free easycsv memory */ -void easycsv_free(easycsv*); +void +easycsv_free(easycsv*); -/* Insert in */ -// int easycsv_insertrow(struct easycsv*, const char*, const int); +/* GENERIC ALGORITHMS */ -/* Create new column on the RHS of an existing one */ -int easycsv_pushcolumn(easycsv*, const char*); +/* Find value and returns row and column in unsigned int pointers */ +int +easycsv_findvalue(const easycsv*, + const char*, + unsigned int*, + unsigned int*); -/* Insert string in a specific cell */ -int easycsv_insertvalue(const easycsv*, const char*, const unsigned int, const unsigned int); +/* Find number of instances of value */ +int +easycsv_findnumvalue(const easycsv*, + const char*); -/* Insert string in a vacant cell under a named column */ -int easycsv_pushcolumnvalue(const easycsv*, const char*, const char*); +int +easycsv_sortrow(const easycsv*, + const char*, + const EASYCSV_SORT); -/* Insert string in a specific cell with named column in row 1 */ -int easycsv_insertcolumnvalue(const easycsv*, const char*, const unsigned int, const char*, const EASYCSV_VALMODE); - -/* Insert array of string pointers */ -int easycsv_pushcolumnstrarray(const easycsv*, const char*[], const char*); - -/* Insert array of void pointers */ -int easycsv_pushcolumnvoidarray(const easycsv*, const void*[], const char*); - -/* Delete CSV value */ -int easycsv_deletevalue(const easycsv*, const unsigned int, const unsigned int); - -/* Delete column with unsigned int */ -int easycsv_deletecolumnint(const easycsv*, const unsigned int); - -/* Delete column with str */ -int easycsv_deletecolumnstr(const easycsv*, const char*); - -/* Delete column value with str */ -int easycsv_deletecolumnstrvalue(const easycsv*, const char*, const char*); +int +easycsv_sortcolumn(const easycsv*, + const char*, const + EASYCSV_SORT); /* Append to CSV files */ -int easycsv_appendcsv(easycsv*, easycsv*); +int +easycsv_appendcsv(easycsv*, + easycsv*); + +/* READ VALUE */ /* Read string in a specific cell */ -const char *easycsv_readvalue(const easycsv*, const unsigned int, const unsigned int); +char* +easycsv_readvalue(const easycsv*, + const unsigned int, + const unsigned int); /* Read string in a specific cell with named column in row 1 */ -const char *easycsv_readcolumnvalue(const easycsv*, const char*, const unsigned int); +char* +easycsv_readcolumnvalue(const easycsv*, + const char*, + const unsigned int); /* Number of rows in entire CSV file */ -int easycsv_printrows(const easycsv*); +int +easycsv_printrows(const easycsv*); /* Number of columns in entire CSV file */ -int easycsv_printcolumns(const easycsv*); +int +easycsv_printcolumns(const easycsv*); + +/* INSERT VALUE -- AT SPECIFIC ROW AND COLUMN */ + +// int easycsv_insertrow(struct easycsv*, const char*, const int); + +/* Insert string in a specific cell */ +int +easycsv_insertvalue(easycsv*, + const char*, + const unsigned int, + const unsigned int); + +/* Insert string in a specific cell with specific mode */ +int +easycsv_insertvaluemode(easycsv*, + const char*, + const unsigned int, + const unsigned int, + const EASYCSV_VALMODE); + +/* Insert string in a specific cell with named column in row 1 */ +int +easycsv_insertcolumnvalue(easycsv*, + const char*, + const unsigned int, + const char*); + +/* Insert string in a specific cell with named column in row 1 with specific mode */ +int +easycsv_insertcolumnvaluemode(easycsv*, + const char*, + const unsigned int, + const char*, + const EASYCSV_VALMODE); + +/* PUSH VALUE */ + +/* Create new column on the RHS of an existing one */ +int +easycsv_pushcolumn(easycsv*, + const char*); + +/* Insert string in a vacant cell under a named column */ +int +easycsv_pushcolumnvalue(easycsv*, + const char*, + const char*); + +/* Insert array of string pointers */ +int +easycsv_pushcolumnstrarr(easycsv*, + const char*[], + const char*); + +/* Insert array of void pointers */ +int +easycsv_pushcolumnvoidarr(easycsv*, + const void*[], + const char*); + +/* DELETE VALUES -- use INSERT VALUE with empty string*/ + +/* Delete CSV value */ +int +easycsv_deletevalue(easycsv*, + const unsigned int, + const unsigned int); + +/* Delete numbered column */ +int +easycsv_deletecolumnint(const easycsv*, + const unsigned int); + +/* Delete named column */ +int +easycsv_deletecolumnstr(const easycsv*, + const char*); + +/* Delete column value with str */ +int +easycsv_deletecolumnstrvalue(const easycsv*, + const char*, + const char*); #endif /* EASYCSV_H */ diff --git a/tests/01 b/tests/01 deleted file mode 100755 index 5be12dc0eeab98d628c869725b1995b63858c2b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13456 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 E1ALs>5&!@I diff --git a/tests/01.c b/tests/01/01.c similarity index 100% rename from tests/01.c rename to tests/01/01.c diff --git a/tests/csv/01.csv b/tests/01/01.csv similarity index 100% rename from tests/csv/01.csv rename to tests/01/01.csv diff --git a/tests/02.c b/tests/02/02.c similarity index 100% rename from tests/02.c rename to tests/02/02.c