From 0db5c51ed9a3a0e210a8bd6603fa7e828bb6db58 Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Mon, 22 Jun 2020 22:16:58 +0200 Subject: [PATCH] Add a simple CSV writer for Python's "excel" csv dialect --- CMakeLists.txt | 3 +++ src/engine/shared/csv.cpp | 40 ++++++++++++++++++++++++++++ src/engine/shared/csv.h | 8 ++++++ src/test/csv.cpp | 56 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+) create mode 100644 src/engine/shared/csv.cpp create mode 100644 src/engine/shared/csv.h create mode 100644 src/test/csv.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 395451b81..2def8e284 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -731,6 +731,8 @@ set_src(ENGINE_SHARED GLOB src/engine/shared config_variables.h console.cpp console.h + csv.cpp + csv.h datafile.cpp datafile.h demo.cpp @@ -1336,6 +1338,7 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST) set_src(TESTS GLOB src/test aio.cpp color.cpp + csv.cpp datafile.cpp fs.cpp git_revision.cpp diff --git a/src/engine/shared/csv.cpp b/src/engine/shared/csv.cpp new file mode 100644 index 000000000..0fc90a879 --- /dev/null +++ b/src/engine/shared/csv.cpp @@ -0,0 +1,40 @@ +#include "csv.h" + +void CsvWrite(IOHANDLE File, int NumColumns, const char *const *ppColumns) +{ + for(int i = 0; i < NumColumns; i++) + { + if(i != 0) + { + io_write(File, ",", 1); + } + const char *pColumn = ppColumns[i]; + int ColumnLength = str_length(pColumn); + if(!str_find(pColumn, "\"") && !str_find(pColumn, ",")) + { + io_write(File, pColumn, ColumnLength); + continue; + } + + int Start = 0; + io_write(File, "\"", 1); + for(int j = 0; j < ColumnLength; j++) + { + if(pColumn[j] == '"') + { + if(Start != j) + { + io_write(File, pColumn + Start, j - Start); + } + Start = j + 1; + io_write(File, "\"\"", 2); + } + } + if(Start != ColumnLength) + { + io_write(File, pColumn + Start, ColumnLength - Start); + } + io_write(File, "\"", 1); + } + io_write_newline(File); +} diff --git a/src/engine/shared/csv.h b/src/engine/shared/csv.h new file mode 100644 index 000000000..aa5273382 --- /dev/null +++ b/src/engine/shared/csv.h @@ -0,0 +1,8 @@ +#ifndef ENGINE_SHARED_CSV_H +#define ENGINE_SHARED_CSV_H + +#include + +void CsvWrite(IOHANDLE File, int NumColumns, const char *const *pColumns); + +#endif // ENGINE_SHARED_CSV_H diff --git a/src/test/csv.cpp b/src/test/csv.cpp new file mode 100644 index 000000000..bd2e6cf1f --- /dev/null +++ b/src/test/csv.cpp @@ -0,0 +1,56 @@ +#include "test.h" +#include + +#include + +static void Expect(int NumColumns, const char *const *ppColumns, const char *pExpected) +{ + CTestInfo Info; + + IOHANDLE File = io_open(Info.m_aFilename, IOFLAG_WRITE); + ASSERT_TRUE(File); + CsvWrite(File, NumColumns, ppColumns); + io_close(File); + + char aBuf[1024]; + File = io_open(Info.m_aFilename, IOFLAG_READ); + ASSERT_TRUE(File); + int Read = io_read(File, aBuf, sizeof(aBuf)); + io_close(File); + fs_remove(Info.m_aFilename); + + ASSERT_TRUE(Read >= 1); + Read -= 1; + ASSERT_EQ(aBuf[Read], '\n'); + aBuf[Read] = 0; + + #if defined(CONF_FAMILY_WINDOWS) + ASSERT_TRUE(Read >= 1); + Read -= 1; + ASSERT_EQ(aBuf[Read], '\r'); + aBuf[Read] = 0; + #endif + + for(int i = 0; i < Read; i++) + { + EXPECT_NE(aBuf[i], 0); + } + EXPECT_STREQ(aBuf, pExpected); +} + +TEST(Csv, Simple) +{ + const char *apCols1[] = {"a", "b"}; Expect(2, apCols1, "a,b"); + const char *apCols2[] = {"こんにちは"}; Expect(1, apCols2, "こんにちは"); + const char *apCols3[] = {"я", "", "й"}; Expect(3, apCols3, "я,,й"); + const char *apCols4[] = {""}; Expect(1, apCols4, ""); + const char *apCols5[] = {0}; Expect(0, apCols5, ""); +} + +TEST(Csv, LetTheQuotingBegin) +{ + const char *apCols1[] = {"\""}; Expect(1, apCols1, "\"\"\"\""); + const char *apCols2[] = {","}; Expect(1, apCols2, "\",\""); + const char *apCols3[] = {",,", ",\"\"\""}; Expect(2, apCols3, "\",,\",\",\"\"\"\"\"\"\""); + const char *apCols4[] = {"\",", " "}; Expect(2, apCols4, "\"\"\",\", "); +}