/** * Copyright (c) 2021 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "csv.h" csvbufferresult_t csvBuffer( assetbuffer_t *asset, csvbuffercallback_t *callback, void *user ) { int32_t cellChar, read, i, currentColumnCount; char buffer[CSV_BUFFER_SIZE]; char cell[CSV_CELL_SIZE_MAX]; char c; bool callbackResponse; bool insideEncapsulation = false; csvbufferresult_t result = { .cellCount = 0, .rowCount = 0, .columnCount = 0 }; // Init the cell Char Index. cellChar = 0; currentColumnCount = 0; // Begin buffering. while(true) { // Read n bytes into our buffer read = assetBufferRead(asset, buffer, CSV_BUFFER_SIZE); // Now read back those bytes. for(i = 0; i < read; i++) { c = buffer[i]; // Characters we flat out ignore if(c == '\r') continue; // Handle quote marks. if(c == '"') { if(buffer[i+1] == '"') {// "" means a single quote (double-escaped) i++; cell[cellChar++] = c; } else if(insideEncapsulation) { insideEncapsulation = false; } else { insideEncapsulation = true; } continue; } // Is the start of a new cell/row? if(c == '\0' || (!insideEncapsulation && (c == ',' || c == '\n'))) { cell[cellChar] = '\0';// Terminate Cell string // Fire off the callback if(callback != NULL) { callbackResponse = callback(asset, user, result.rowCount, currentColumnCount, cell); if(!callbackResponse) return result; } // Prepare for next row/cell currentColumnCount++; result.columnCount = mathMax(currentColumnCount, result.columnCount); if(c == '\n') { result.rowCount++; currentColumnCount = 0; } result.cellCount++;// Only count cells with cellChar = 0; continue;// Skip } // Add character to the cell. cell[cellChar++] = c; } if(read < CSV_BUFFER_SIZE) break; } // If this is an empty row we don't count it, otherwise we do. if(currentColumnCount != 0) result.rowCount++; return result; } bool _csvBufferRowParserCallback( assetbuffer_t *asset, void *user, int32_t row, int32_t column, char *data ) { csvbufferrowdata_t *rowData = (csvbufferrowdata_t *)user; // Now did we change rows? if(row != rowData->row) { // Yes we did, let's buffer the previous row. if(rowData->callback != NULL) { if(!rowData->callback( asset, rowData->user, rowData->row, &rowData->rowCurrent )) return false; } // Begin next row rowData->row = row; rowData->rowCurrent.columnCount = 0; } // Determine string info for the cell int32_t length = (int32_t)strlen(data); int32_t offset = (column * CSV_CELL_SIZE_MAX); // Now copy the string data to the buffer arrayCopy(sizeof(char), data, length + 1, rowData->rowCurrent.data + offset); // Update the pointer to the string rowData->rowCurrent.columns[column] = rowData->rowCurrent.data + offset; rowData->rowCurrent.columnCount++; return true; } csvbufferresult_t csvBufferRow( assetbuffer_t *asset, csvbufferrowcallback_t *callback, void *user ) { csvbufferrowdata_t data; csvbufferresult_t result; data.row = 0; data.user = user; data.callback = callback; data.rowCurrent.columnCount = 0; // Perform a per-cell buffer and run the parser callback. result = csvBuffer(asset, &_csvBufferRowParserCallback, &data); // Because the buffer may not fire for the last row we handle it here. if(data.rowCurrent.columnCount > 0 && callback != NULL) { if(!callback(asset, user, data.row, &data.rowCurrent)) return result; } return result; } bool _csvBufferRowWithHeadersCallback( assetbuffer_t *asset, void *user, int32_t row, csvrow_t *csv ) { csvbufferrowwithheadersdata_t *data = (csvbufferrowwithheadersdata_t *)user; // Take the headers for row 0 if(row == 0) { csvRowPopulate(csv, &data->headerRow); return true; } // Fire the callback return data->callback(asset, data->user, row, &data->headerRow, csv); } csvbufferresult_t csvBufferRowWithHeaders( assetbuffer_t *asset, csvbufferrowwitheaderscallback_t *callback, void *user ) { csvbufferrowwithheadersdata_t data; data.user = user; data.callback = callback; return csvBufferRow(asset, &_csvBufferRowWithHeadersCallback, &data); } void csvRowPopulate(csvrow_t *source, csvrow_t *dest) { int32_t i; dest->columnCount = source->columnCount; // Copy the raw characters from the source buffer. arrayCopy(sizeof(char), source->data, CSV_ROW_CHARACTERS_MAX, dest->data); // Now update the destination pointers to reference the data buffer. for(i = 0; i < source->columnCount; i++) { dest->columns[i] = dest->data + (i * CSV_CELL_SIZE_MAX); } } bool _csvHeadersGetCallback( assetbuffer_t *asset, void *user, int32_t row, csvrow_t *current ) { csvrow_t *rowData = (csvrow_t *)user; csvRowPopulate(current, rowData); return false;// False to break the loop } csvbufferresult_t csvHeadersGet(assetbuffer_t *asset, csvrow_t *row) { return csvBufferRow(asset, &_csvHeadersGetCallback, row); } int32_t csvColumnGetIndex(csvrow_t *row, char *key) { return arrayFindString(row->columns, row->columnCount, key); } bool _csvRowSearchCallback( assetbuffer_t *asset, void *user, int32_t row, csvrow_t *csv ) { csvsearchdata_t *data = (csvsearchdata_t *)user; // Does the search match? if(strcmp(csv->columns[data->column], data->value) != 0) return true; // Matched, copy and end. csvRowPopulate(csv, data->row); data->rowIndex = row; return false; } int32_t csvRowSearch( assetbuffer_t *asset, csvrow_t *row, int32_t column, char *value ) { csvsearchdata_t data = { .column = column, .row = row, .rowIndex = -1, .value = value }; csvBufferRow(asset, &_csvRowSearchCallback, &data); return data.rowIndex; }