You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1177 lines
26 KiB
1177 lines
26 KiB
#include "easycsv.h" |
|
|
|
/* 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_FLOAT, |
|
EASYCSV_UNKNOWNTYPE |
|
} _EASYCSV_TYPE; |
|
|
|
/* Error flags */ |
|
typedef enum { |
|
|
|
/* Generic errors */ |
|
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_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 = 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 |
|
|
|
/* User-facing failure */ |
|
EASYCSV_PUSHCOLFAIL, // push column value 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 |
|
char *fp; |
|
char *tmpfp; |
|
unsigned int rows; |
|
unsigned int cols; |
|
EASYCSV_ERRORMSG error; |
|
#ifdef EASYCSV_DEBUG |
|
clock_t start; |
|
#endif |
|
} _easycsv; |
|
|
|
/* PRIVATE FUNCTIONS */ |
|
|
|
/* (Constructor) Initialise _easycsv */ |
|
static _easycsv* |
|
_easycsv_priv_init(const char*, |
|
const EASYCSV_MODE, |
|
const EASYCSV_ERRORMSG); |
|
|
|
/* (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*); |
|
|
|
/* Rewind easycsv, checks for readability */ |
|
static int |
|
_easycsv_rewind(_easycsv*, |
|
const EASYCSV_MODE); |
|
|
|
/* 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*); |
|
|
|
/* 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); |
|
|
|
/* 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); |
|
|
|
/* 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 == */ |
|
|
|
/* CONSTRUCTORS AND DESTRUCTORS */ |
|
|
|
/* (Constructor) Initialise easycsv */ |
|
easycsv* |
|
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) |
|
{ |
|
#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; |
|
} |
|
|
|
/* (Destructor) Free easycsv memory */ |
|
void |
|
easycsv_free(easycsv *csv) |
|
{ |
|
/* 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; |
|
|
|
EASYCSV_ERRORMSG temp_error = csv->_priv->error; |
|
csv->_priv->error = EASYCSV_ERROROFF; |
|
for (row = 2; row <= csv->_priv->cols; row++) |
|
if (easycsv_readvalue(csv, colnum, row) != NULL) |
|
break; |
|
csv->_priv->error = temp_error; |
|
|
|
/* All rows are filled, generate new row */ |
|
if (row > csv->_priv->cols) |
|
csv->_priv->rows++; |
|
|
|
/* ROW WILL NOT BE GENERATED \\ row < csv->_priv->rows */ |
|
|
|
return easycsv_insertvalue(csv, val, colnum, row); |
|
} |
|
|
|
/* DELETE VALUES */ |
|
|
|
int |
|
easycsv_deletevalue(easycsv *csv, |
|
const unsigned int col, |
|
const unsigned int row) |
|
{ |
|
return easycsv_insertvalue(csv, "", col, row); |
|
} |
|
|
|
int |
|
easycsv_deletecolumnint(easycsv *csv, |
|
const unsigned int col) |
|
{ |
|
/* ARGS CHECK */ |
|
if (csv == NULL) { |
|
_easycsv_printerror(csv->_priv, EASYCSV_NULLCSV); |
|
return -1; |
|
} |
|
|
|
if (col == 0) { |
|
_easycsv_printerror(csv->_priv, EASYCSV_ZEROCOL); |
|
return -1; |
|
} |
|
|
|
if (col > csv->_priv->cols) { |
|
_easycsv_printerror(csv->_priv, EASYCSV_OVERMAXCOL); |
|
return -1; |
|
} |
|
/* END ARGS CHECK */ |
|
|
|
for (unsigned int i = 1; i <= csv->_priv->cols; i++) { |
|
if (easycsv_readvalue(csv, col, i) == NULL) |
|
break; |
|
if (easycsv_deletevalue(csv, col, i) < 0) |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int |
|
easycsv_deletecolumnstr(easycsv *csv, |
|
const char *col) |
|
{ |
|
/* ARGS CHECK */ |
|
if (_easycsv_checkcsvandstring_one(csv, col) < 0) |
|
return -1; |
|
/* END ARGS CHECK */ |
|
|
|
int colnum = _easycsv_getcolumn(csv, col); |
|
if (colnum < 0) |
|
return -1; |
|
|
|
return easycsv_deletecolumnint(csv, colnum); |
|
} |
|
|
|
int |
|
easycsV_deleterowint(easycsv *csv, |
|
const unsigned int row) |
|
{ |
|
return 0; |
|
} |
|
/* PRIVATE FUNCTIONS */ |
|
|
|
/* (Constructor) Initialise _easycsv */ |
|
_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 or 0 |
|
|
|
_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_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 |
|
|
|
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 |
|
|
|
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->_priv, EASYCSV_NULLCSV); |
|
return -1; |
|
} |
|
/* ARGS CHECK */ |
|
|
|
/* 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; |
|
} |
|
|
|
/* Copy entire file */ |
|
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; |
|
} |
|
|
|
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; |
|
} |
|
|
|
if (row > csv->_priv->rows) { |
|
_easycsv_printerror(csv->_priv, EASYCSV_OVERMAXROW); |
|
return NULL; |
|
} |
|
|
|
if (row == 0) { |
|
_easycsv_printerror(csv->_priv, EASYCSV_ZEROROW); |
|
return NULL; |
|
} |
|
/* END ARGS CHECK */ |
|
|
|
/* 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) |
|
{ |
|
/* 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->_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 _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) |
|
{ |
|
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->_priv, EASYCSV_NULLCSV); |
|
return -1; |
|
} |
|
|
|
if (one == NULL) { |
|
_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) { |
|
_easycsv_printerror(csv->_priv, EASYCSV_EMPTYSTRING); |
|
return -1; |
|
} |
|
|
|
return 0; |
|
}
|
|
|