Initial commit
This commit is contained in:
commit
e1d1e9f723
824
src/easycsv.c
Normal file
824
src/easycsv.c
Normal file
@ -0,0 +1,824 @@
|
|||||||
|
#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;
|
||||||
|
}
|
878
src/easycsv.c~
Normal file
878
src/easycsv.c~
Normal file
@ -0,0 +1,878 @@
|
|||||||
|
#include "easycsv.h"
|
||||||
|
|
||||||
|
/* Flags */
|
||||||
|
typedef enum {
|
||||||
|
EASYCSV_NULL,
|
||||||
|
EASYCSV_STRING,
|
||||||
|
EASYCSV_INT
|
||||||
|
} _EASYCSV_TYPE;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
|
||||||
|
/* Generic errors */
|
||||||
|
EASYCSV_NOERROR = 0, // no error
|
||||||
|
EASYCSV_NULLPTR, // pointer is NULL
|
||||||
|
EASYCSV_EMPTYSTRING, // empty string
|
||||||
|
EASYCSV_OVERMAXROW, // int exceeds row limit
|
||||||
|
EASYCSV_OVERMAXCOL, // int exceeds col limit
|
||||||
|
|
||||||
|
/* 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*);
|
||||||
|
|
||||||
|
/* 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_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 (csv == NULL) {
|
||||||
|
fprintf(stderr, "easycsv: csv is NULL in %s\n", csv->_priv->fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((col == NULL) || (col[0] == '\0')) {
|
||||||
|
fprintf(stderr, "easycsv: col is either NULL or empty in %s\n", csv->_priv->fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (csv->mode == EASYCSV_R) {
|
||||||
|
#ifdef EASYCSV_DEBUG
|
||||||
|
fprintf(stderr, "[%i] ", clock() - csv->_priv->start);
|
||||||
|
#endif
|
||||||
|
fprintf(stderr, "easycsv: %s set to read\n", csv->_priv->fp);
|
||||||
|
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) {
|
||||||
|
#ifdef EASYCSV_DEBUG
|
||||||
|
fprintf(stderr, "[%i] ", clock() - csv->_priv->start);
|
||||||
|
#endif
|
||||||
|
fprintf(stderr, "easycsv: failed to push column %s in %s\n", col, csv->_priv->fp);
|
||||||
|
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) {
|
||||||
|
#ifdef EASYCSV_DEBUG
|
||||||
|
fprintf(stderr, "[%i] ", clock() - csv->_priv->start);
|
||||||
|
#endif
|
||||||
|
fprintf(stderr, "easycsv: failed to push column %s in %s\n", col, csv->_priv->tmpfp);
|
||||||
|
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) {
|
||||||
|
#ifdef EASYCSV_DEBUG
|
||||||
|
fprintf(stderr, "[%i] ", clock() - csv->_priv->start);
|
||||||
|
#endif
|
||||||
|
fprintf(stderr, "easycsv: failed to update %s\n", csv->_priv->fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
easycsv_pushcolumnvalue(const easycsv *csv, const char *col, const char *val)
|
||||||
|
{
|
||||||
|
if (csv == NULL) {
|
||||||
|
fprintf(stderr, "easycsv: csv is NULL in %s\n", csv->_priv->fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((col == NULL) || (col[0] == '\0')) {
|
||||||
|
fprintf(stderr, "easycsv: col is either NULL or empty in %s\n", csv->_priv->fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((val == NULL) || (val[0] == '\0')) {
|
||||||
|
fprintf(stderr, "easycsv: val is either NULL or empty in %s\n", csv->_priv->fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int colnum = _easycsv_getcolumn(csv, col);
|
||||||
|
|
||||||
|
if (colnum < 0) {
|
||||||
|
fprintf(stderr, "easycsv: error occured when fetcing col number in %s", csv->_priv->fp);
|
||||||
|
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) {
|
||||||
|
fprintf(stderr, "easycsv: failed to set file pointer to end in %s", csv->_priv->fp);
|
||||||
|
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 (csv == NULL) {
|
||||||
|
fprintf(stderr, "easycsv: csv is NULL in %s\n", csv->_priv->fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((col == NULL) || (col[0] == '\0')) {
|
||||||
|
fprintf(stderr, "easycsv: col is either NULL or empty in %s\n", csv->_priv->fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((val == NULL) || (val[0] == '\0')) {
|
||||||
|
fprintf(stderr, "easycsv: val is either NULL or empty in %s\n", csv->_priv->fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int colnum = _easycsv_getcolumn(csv, col);
|
||||||
|
if (colnum < 0) {
|
||||||
|
fprintf(stderr, "easycsv: failed to get colnum of %s in %s", col, csv->_priv->fp);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (_priv == NULL) {
|
||||||
|
fprintf(stderr, "easycsv: csv is NULL in %s\n", _priv->fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prepare it for reading */
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (_priv == NULL) {
|
||||||
|
fprintf(stderr, "easycsv: csv is NULL in %s\n", _priv->fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 (csv == NULL) {
|
||||||
|
fprintf(stderr, "easycsv: csv is NULL in %s\n", csv->_priv->fp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row > csv->_priv->rows) {
|
||||||
|
fprintf(stderr, "easycsv: row exceeds max in %s\n", csv->_priv->fp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = _easycsv_getcolumn(csv, col);
|
||||||
|
if (i < 1) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return easycsv_readvalue(csv, i, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char*
|
||||||
|
easycsv_readvalue(const easycsv *csv, const unsigned int col, const unsigned int row)
|
||||||
|
{
|
||||||
|
if (col == 0 || row == 0) {
|
||||||
|
fputs("easycsv: row or col is 0\n", stderr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *str = _easycsv_getrow(csv, row);
|
||||||
|
if (str == NULL) {
|
||||||
|
fprintf(stderr, "easycsv: row %i does not exist in %s\n", row, csv->_priv->fp);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
easycsv_geterror(const easycsv *csv)
|
||||||
|
{
|
||||||
|
_easycsv_pgeterror(csv->_priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
easycsv_fgeterror(const easycsv *csv, FILE *fs)
|
||||||
|
{
|
||||||
|
_easycsv_fpgeterror(csv->_priv, fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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) {
|
||||||
|
fputs("easycsv: csv is NULL\n", stderr);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set temp file to read binary */
|
||||||
|
if (freopen(csv->_priv->tmpfp, "rb", csv->_priv->temp) == NULL) {
|
||||||
|
#ifdef EASYCSV_DEBUG
|
||||||
|
fprintf(stderr, "[%i] ", clock() - csv->_priv->start);
|
||||||
|
#endif
|
||||||
|
fprintf(stderr, "easycsv: failed to reopen temp file %s in read binary mode\n", csv->_priv->fp);
|
||||||
|
easycsv_free(csv);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set file to write binary */
|
||||||
|
if (freopen(csv->_priv->fp, "wb", csv->_priv->file) == NULL) {
|
||||||
|
#ifdef EASYCSV_DEBUG
|
||||||
|
fprintf(stderr, "[%i] ", clock() - csv->_priv->start);
|
||||||
|
#endif
|
||||||
|
fprintf(stderr, "easycsv: failed to reopen file %s in write binary mode\n", csv->_priv->tmpfp);
|
||||||
|
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) {
|
||||||
|
#ifdef EASYCSV_DEBUG
|
||||||
|
fprintf(stderr, "[%i] ", clock() - csv->_priv->start);
|
||||||
|
#endif
|
||||||
|
fprintf(stderr, "easycsv: failed to reopen temp file %s in write mode\n", csv->_priv->fp);
|
||||||
|
easycsv_free(csv);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set file back to read */
|
||||||
|
if (freopen(csv->_priv->fp, "r", csv->_priv->file) == NULL) {
|
||||||
|
#ifdef EASYCSV_DEBUG
|
||||||
|
fprintf(stderr, "[%i] ", clock() - csv->_priv->start);
|
||||||
|
#endif
|
||||||
|
fprintf(stderr, "easycsv: failed to reopen file %s in read mode\n", csv->_priv->tmpfp);
|
||||||
|
easycsv_free(csv);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char*
|
||||||
|
_easycsv_getrow(const easycsv *csv, const unsigned int row)
|
||||||
|
{
|
||||||
|
if (csv == NULL) {
|
||||||
|
fputs("easycsv: csv is NULL\n", stderr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row > csv->_priv->rows) {
|
||||||
|
fputs("easycsv: row overflow\n", stderr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row == 0) {
|
||||||
|
fputs("easycsv: row is 0\n", stderr);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (_priv == NULL) {
|
||||||
|
fprintf(stderr, "easycsv: csv is NULL in %s\n", _priv->fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if file is readable */
|
||||||
|
if (mode == EASYCSV_R) {
|
||||||
|
#ifdef EASYCSV_DEBUG
|
||||||
|
fprintf(stderr, "[%i] ", clock() - _priv->start);
|
||||||
|
#endif
|
||||||
|
fprintf(stderr, "easycsv: %s is not be readable\n", _priv->fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set file pointer to the start */
|
||||||
|
rewind(_priv->file);
|
||||||
|
|
||||||
|
/* Check if empty file */
|
||||||
|
if (fscanf(_priv->file, "\n") == EOF) {
|
||||||
|
#ifdef EASYCSV_DEBUG
|
||||||
|
fprintf(stderr, "[%i] ", clock() - _priv->start);
|
||||||
|
#endif
|
||||||
|
fprintf(stderr, "easycsv: %s is empty\n", _priv->fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_easycsv_getcolumn(const easycsv *csv, const char *col)
|
||||||
|
{
|
||||||
|
if (csv == NULL) {
|
||||||
|
fputs("easycsv: csv is NULL\n", stderr);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((col == NULL) || (col[0] == '\0')) {
|
||||||
|
fputs("easycsv: col is either NULL or empty\n", stderr);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grab str of row 1 */
|
||||||
|
char *firstrow = _easycsv_getrow(csv, 1);
|
||||||
|
|
||||||
|
if (firstrow == NULL) {
|
||||||
|
#ifdef EASYCSV_DEBUG
|
||||||
|
fprintf(stderr, "[%i] ", clock() - csv->_priv->start);
|
||||||
|
#endif
|
||||||
|
fprintf(stderr, "easycsv: row 1 does not exist in %s\n", csv->_priv->fp);
|
||||||
|
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) {
|
||||||
|
#ifdef EASYCSV_DEBUG
|
||||||
|
fprintf(stderr, "[%i] ", clock() - csv->_priv->start);
|
||||||
|
#endif
|
||||||
|
fprintf(stderr, "easycsv: column %s not found in %s\n", col, csv->_priv->fp);
|
||||||
|
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_ERRORSTDERR: _easycsv_geterror(_priv, error, stderr); break;
|
||||||
|
case EASYCSV_ERRORSTDOUT: _easycsv_geterror(_priv, error, stdout); break;
|
||||||
|
default: 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_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_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", fs); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_priv->fp != NULL) {
|
||||||
|
fprintf(fs, " in %s", _priv->fp);
|
||||||
|
}
|
||||||
|
}
|
102
src/easycsv.h
Normal file
102
src/easycsv.h
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
#ifndef EASYCSV_H
|
||||||
|
#define EASYCSV_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/sendfile.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
/* Flags for error messages */
|
||||||
|
typedef enum {
|
||||||
|
EASYCSV_ERROROFF,
|
||||||
|
EASYCSV_ERRORSTDERR,
|
||||||
|
EASYCSV_ERRORSTDOUT
|
||||||
|
} EASYCSV_ERRORMSG;
|
||||||
|
|
||||||
|
/* Using our own flags to facilitate changing modes during file copying phases */
|
||||||
|
typedef enum {
|
||||||
|
EASYCSV_R,
|
||||||
|
EASYCSV_W
|
||||||
|
} EASYCSV_MODE;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EASYCSV_REPLACE,
|
||||||
|
EASYCSV_APPEND,
|
||||||
|
EASYCSV_APPEND_R,
|
||||||
|
} EASYCSV_VALMODE;
|
||||||
|
|
||||||
|
typedef struct _easycsv _easycsv;
|
||||||
|
|
||||||
|
typedef struct easycsv {
|
||||||
|
EASYCSV_MODE mode;
|
||||||
|
_easycsv *_priv; // private members
|
||||||
|
} easycsv;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ============================
|
||||||
|
* FUNCTION PROTOTYPES - PUBLIC
|
||||||
|
* ============================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* (Constructor) Initialise easycsv */
|
||||||
|
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);
|
||||||
|
|
||||||
|
/* (Destructor) Free easycsv memory */
|
||||||
|
void easycsv_free(easycsv*);
|
||||||
|
|
||||||
|
/* Insert in */
|
||||||
|
// int easycsv_insertrow(struct easycsv*, const char*, const int);
|
||||||
|
|
||||||
|
/* Create new column on the RHS of an existing one */
|
||||||
|
int easycsv_pushcolumn(easycsv*, const char*);
|
||||||
|
|
||||||
|
/* Insert string in a specific cell */
|
||||||
|
int easycsv_insertvalue(const easycsv*, const char*, const unsigned int, const unsigned int);
|
||||||
|
|
||||||
|
/* Insert string in a vacant cell under a named column */
|
||||||
|
int easycsv_pushcolumnvalue(const easycsv*, const char*, const char*);
|
||||||
|
|
||||||
|
/* 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*);
|
||||||
|
|
||||||
|
/* Append to CSV files */
|
||||||
|
int easycsv_appendcsv(easycsv*, easycsv*);
|
||||||
|
|
||||||
|
/* Read string in a specific cell */
|
||||||
|
const 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);
|
||||||
|
|
||||||
|
/* Number of rows in entire CSV file */
|
||||||
|
int easycsv_printrows(const easycsv*);
|
||||||
|
|
||||||
|
/* Number of columns in entire CSV file */
|
||||||
|
int easycsv_printcolumns(const easycsv*);
|
||||||
|
|
||||||
|
#endif /* EASYCSV_H */
|
90
src/easycsv.h~
Normal file
90
src/easycsv.h~
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
#ifndef EASYCSV_H
|
||||||
|
#define EASYCSV_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/sendfile.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
/* Flags for error messages */
|
||||||
|
typedef enum {
|
||||||
|
EASYCSV_ERROROFF,
|
||||||
|
EASYCSV_ERRORSTDERR,
|
||||||
|
EASYCSV_ERRORSTDOUT
|
||||||
|
} EASYCSV_ERRORMSG;
|
||||||
|
|
||||||
|
/* Using our own flags to facilitate changing modes during file copying phases */
|
||||||
|
typedef enum {
|
||||||
|
EASYCSV_R,
|
||||||
|
EASYCSV_W
|
||||||
|
} EASYCSV_MODE;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EASYCSV_REPLACE,
|
||||||
|
EASYCSV_APPEND,
|
||||||
|
EASYCSV_APPEND_R,
|
||||||
|
} EASYCSV_VALMODE;
|
||||||
|
|
||||||
|
typedef struct _easycsv _easycsv;
|
||||||
|
|
||||||
|
typedef struct easycsv {
|
||||||
|
EASYCSV_MODE mode;
|
||||||
|
_easycsv *_priv; // private members
|
||||||
|
} easycsv;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ============================
|
||||||
|
* FUNCTION PROTOTYPES - PUBLIC
|
||||||
|
* ============================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* (Constructor) Initialise easycsv */
|
||||||
|
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);
|
||||||
|
|
||||||
|
/* (Destructor) Free easycsv memory */
|
||||||
|
void easycsv_free(easycsv*);
|
||||||
|
|
||||||
|
/* Insert in */
|
||||||
|
// int easycsv_insertrow(struct easycsv*, const char*, const int);
|
||||||
|
|
||||||
|
/* Create new column on the RHS of an existing one */
|
||||||
|
int easycsv_pushcolumn(easycsv*, const char*);
|
||||||
|
|
||||||
|
/* Insert string in a specific cell */
|
||||||
|
int easycsv_insertvalue(const easycsv*, const char*, const unsigned int, const unsigned int);
|
||||||
|
|
||||||
|
/* Insert string in a vacant cell under a named column */
|
||||||
|
int easycsv_pushcolumnvalue(const easycsv*, const char*, const char*);
|
||||||
|
|
||||||
|
/* 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*);
|
||||||
|
|
||||||
|
/* Append to CSV files */
|
||||||
|
int easycsv_appendcsv(easycsv*, easycsv*);
|
||||||
|
|
||||||
|
/* Read string in a specific cell */
|
||||||
|
const 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);
|
||||||
|
|
||||||
|
/* Number of rows in entire CSV file */
|
||||||
|
int easycsv_printrows(const easycsv*);
|
||||||
|
|
||||||
|
/* Number of columns in entire CSV file */
|
||||||
|
int easycsv_printcolumns(const easycsv*);
|
||||||
|
|
||||||
|
#endif /* EASYCSV_H */
|
0
src/internal/easycsv_error.h
Normal file
0
src/internal/easycsv_error.h
Normal file
44
tests/01.c
Normal file
44
tests/01.c
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#include "../include/easycsv/easycsv.h"
|
||||||
|
|
||||||
|
#define CSV_FP "01.csv"
|
||||||
|
#define CSV_MODE "r"
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc < 2) {
|
||||||
|
fprintf(stderr, "No arguments!\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct easycsv *csv = easycsv_init(CSV_FP, CSV_MODE);
|
||||||
|
|
||||||
|
if (access(CSV_FP, F_OK) < 0) {
|
||||||
|
fprintf(stderr, "%s cannot be found!\n", CSV_FP);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (csv == NULL) {
|
||||||
|
fprintf(stderr, "Failed to initialise %s on mode %s\n", CSV_FP, CSV_MODE);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("COLUMNS: %i\nROWS: %i\n", easycsv_rows(csv), easycsv_columns(csv));
|
||||||
|
|
||||||
|
// char *str = malloc(EASYCSV_BUFFER_SIZE);
|
||||||
|
|
||||||
|
printf("Searching column values of %s...\n", argv[1]);
|
||||||
|
|
||||||
|
for (int i = 2;;i++) {
|
||||||
|
char *null = (char*) easycsv_readcolumnvalue(csv, argv[1], i);
|
||||||
|
if (null == NULL) {
|
||||||
|
free(null);
|
||||||
|
puts("End of file!\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
printf("ROW %i: %s\n", i, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
easycsv_free(csv);
|
||||||
|
csv = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
53
tests/02.c
Normal file
53
tests/02.c
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#include "../include/easycsv/easycsv.h"
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
const char CSV_FP[] = "02.csv";
|
||||||
|
|
||||||
|
struct easycsv *csv = easycsv_init(CSV_FP, EASYCSV_W);
|
||||||
|
|
||||||
|
if (csv == NULL) {
|
||||||
|
fprintf(stderr, "Failed to load %s\n", CSV_FP);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char mod_one[] = "first-mod-deluxe-edition";
|
||||||
|
|
||||||
|
const char *paths_one[5] = {
|
||||||
|
"FILEPATH1ONE",
|
||||||
|
"FILEPATH2ONE",
|
||||||
|
"FILEPATH3ONE",
|
||||||
|
"FILEPATH4ONE",
|
||||||
|
"FILEPATH5ONE"
|
||||||
|
};
|
||||||
|
|
||||||
|
const char mod_two[] = "second-mod-gold-edition";
|
||||||
|
|
||||||
|
const char *paths_two[4] = {
|
||||||
|
"FILEPATH1TWO",
|
||||||
|
"FILEPATH2TWO",
|
||||||
|
"FILEPATH3TWO",
|
||||||
|
"FILEPATH4TWO"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (easycsv_pushcolumn(csv, mod_one) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t st = 0; st < 5; st++) {
|
||||||
|
if (easycsv_pushcolumnvalue(csv, mod_one, paths_one[st]) < 0) return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (easycsv_pushcolumn(csv, mod_two) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t st = 0; st < 4; st++) {
|
||||||
|
if (easycsv_pushcolumnvalue(csv, mod_two, paths_two[st]) < 0) return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
easycsv_free(csv);
|
||||||
|
csv = NULL;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
54
tests/02.c~
Normal file
54
tests/02.c~
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#include "../include/easycsv/easycsv.h"
|
||||||
|
|
||||||
|
#define CSV_FP "02.csv"
|
||||||
|
#define CSV_MODE "w"
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
struct easycsv *csv = easycsv_init(CSV_FP, CSV_MODE);
|
||||||
|
|
||||||
|
if (csv == NULL) {
|
||||||
|
fprintf(stderr, "Failed to load %s\n", CSV_FP);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char mod_one[] = "first-mod-deluxe-edition";
|
||||||
|
|
||||||
|
const char *paths_one[5] = {
|
||||||
|
"FILEPATH1ONE",
|
||||||
|
"FILEPATH2ONE",
|
||||||
|
"FILEPATH3ONE",
|
||||||
|
"FILEPATH4ONE",
|
||||||
|
"FILEPATH5ONE"
|
||||||
|
};
|
||||||
|
|
||||||
|
const char mod_two[] = "second-mod-gold-edition";
|
||||||
|
|
||||||
|
const char *paths_two[4] = {
|
||||||
|
"FILEPATH1TWO",
|
||||||
|
"FILEPATH2TWO",
|
||||||
|
"FILEPATH3TWO",
|
||||||
|
"FILEPATH4TWO"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (easycsv_pushcolumn(csv, mod_one) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t st = 0; st < 5; st++) {
|
||||||
|
if (easycsv_pushcolumnvalue(csv, mod_one, paths_one[st]) < 0) return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (easycsv_pushcolumn(csv, mod_two) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t st = 0; st < 4; st++) {
|
||||||
|
if (easycsv_pushcolumnvalue(csv, mod_two, paths_two[st]) < 0) return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
easycsv_free(csv);
|
||||||
|
csv = NULL;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
5
tests/csv/01.csv
Normal file
5
tests/csv/01.csv
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
TEST1,TEST2,TEST3
|
||||||
|
FILEPATHA1,FILEPATHA2,FILEPATHA3
|
||||||
|
FILEPATHB1,FILEPATHB2,FILEPATHB3
|
||||||
|
,FILEPATHC2,FILEPATHC3
|
||||||
|
,FILEPATHD2,
|
|
Loading…
Reference in New Issue
Block a user