From b8a762ec702edff988c952cf7346251888ba5e4a Mon Sep 17 00:00:00 2001 From: MarkLee131 Date: Sun, 29 Mar 2026 02:44:25 +0800 Subject: [PATCH 1/3] fixes auto-quote not quoting cells containing quote characters WriteCsv auto-quote logic checks for separator, space and newline in cell content, but does not check for the quote character itself. This causes cells containing quotes (but no separator/space/newline) to be written unquoted and unescaped, producing malformed CSV output that corrupts data on re-read. Add quote character to the auto-quote condition and add test104 to verify round-trip integrity for cells containing quote characters. --- CMakeLists.txt | 1 + src/rapidcsv.h | 1 + tests/test104.cpp | 81 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 tests/test104.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index cae4c48..4d77232 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -193,6 +193,7 @@ if(RAPIDCSV_BUILD_TESTS) endif() add_unit_test(test102) add_unit_test(test103) + add_unit_test(test104) # perf tests add_perf_test(ptest001) diff --git a/src/rapidcsv.h b/src/rapidcsv.h index 1d98b4e..eafbfcd 100644 --- a/src/rapidcsv.h +++ b/src/rapidcsv.h @@ -1820,6 +1820,7 @@ namespace rapidcsv { if (mSeparatorParams.mAutoQuote && ((itc->find(mSeparatorParams.mSeparator) != std::string::npos) || + (itc->find(mSeparatorParams.mQuoteChar) != std::string::npos) || (itc->find(' ') != std::string::npos) || (itc->find('\n') != std::string::npos))) { diff --git a/tests/test104.cpp b/tests/test104.cpp new file mode 100644 index 0000000..59f5dcb --- /dev/null +++ b/tests/test104.cpp @@ -0,0 +1,81 @@ +// test104.cpp - write round-trip with cells containing quote characters + +#include +#include "unittest.h" + +int main() +{ + int rv = 0; + + try + { + // Test 1: Cell containing quote character but no separator/space/newline + { + std::string path = unittest::TempPath(); + std::string csv = + "a,b,c\n" + "1,he said \"hello\",3\n" + "4,5,6\n" + ; + unittest::WriteFile(path, csv); + + rapidcsv::Document doc1(path, rapidcsv::LabelParams(-1, -1)); + unittest::ExpectEqual(std::string, doc1.GetCell(1, 1), + "he said \"hello\""); + + // Save and reload - data must survive round-trip + doc1.Save(path); + rapidcsv::Document doc2(path, rapidcsv::LabelParams(-1, -1)); + + unittest::ExpectEqual(size_t, doc2.GetRowCount(), size_t(3)); + unittest::ExpectEqual(size_t, doc2.GetColumnCount(), size_t(3)); + unittest::ExpectEqual(std::string, doc2.GetCell(1, 1), + "he said \"hello\""); + + unittest::DeleteFile(path); + } + + // Test 2: Cell containing only a quote character + { + std::string path = unittest::TempPath(); + + std::istringstream ss("a,b\nx,\"y\"\n"); + rapidcsv::Document doc1(ss, rapidcsv::LabelParams(-1, -1)); + doc1.SetCell(1, 0, "\""); + doc1.Save(path); + + rapidcsv::Document doc2(path, rapidcsv::LabelParams(-1, -1)); + unittest::ExpectEqual(std::string, doc2.GetCell(1, 0), "\""); + + unittest::DeleteFile(path); + } + + // Test 3: Empty quoted cells round-trip + { + std::string path = unittest::TempPath(); + std::string csv = "\"\",\"\",\"\"\n"; + unittest::WriteFile(path, csv); + + rapidcsv::Document doc1(path, rapidcsv::LabelParams(-1, -1)); + unittest::ExpectEqual(std::string, doc1.GetCell(0, 0), ""); + unittest::ExpectEqual(std::string, doc1.GetCell(1, 0), ""); + unittest::ExpectEqual(std::string, doc1.GetCell(2, 0), ""); + + doc1.Save(path); + rapidcsv::Document doc2(path, rapidcsv::LabelParams(-1, -1)); + unittest::ExpectEqual(size_t, doc2.GetColumnCount(), size_t(3)); + unittest::ExpectEqual(std::string, doc2.GetCell(0, 0), ""); + unittest::ExpectEqual(std::string, doc2.GetCell(1, 0), ""); + unittest::ExpectEqual(std::string, doc2.GetCell(2, 0), ""); + + unittest::DeleteFile(path); + } + } + catch (const std::exception& ex) + { + std::cout << ex.what() << std::endl; + rv = 1; + } + + return rv; +} From bc39af7c0d722024bdd3acd653ffc536e1169acf Mon Sep 17 00:00:00 2001 From: Kristofer Berggren Date: Sat, 4 Apr 2026 14:35:36 +0800 Subject: [PATCH 2/3] rename test104 -> test106 to prepare for master merge --- CMakeLists.txt | 2 +- tests/{test104.cpp => test106.cpp} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename tests/{test104.cpp => test106.cpp} (97%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d77232..ccec84c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -193,7 +193,7 @@ if(RAPIDCSV_BUILD_TESTS) endif() add_unit_test(test102) add_unit_test(test103) - add_unit_test(test104) + add_unit_test(test106) # perf tests add_perf_test(ptest001) diff --git a/tests/test104.cpp b/tests/test106.cpp similarity index 97% rename from tests/test104.cpp rename to tests/test106.cpp index 59f5dcb..1416528 100644 --- a/tests/test104.cpp +++ b/tests/test106.cpp @@ -1,4 +1,4 @@ -// test104.cpp - write round-trip with cells containing quote characters +// test106.cpp - write round-trip with cells containing quote characters #include #include "unittest.h" From dff3e684a014dee83f0e31f74876aad073889e0f Mon Sep 17 00:00:00 2001 From: Kristofer Berggren Date: Sat, 4 Apr 2026 14:36:41 +0800 Subject: [PATCH 3/3] bump version --- src/rapidcsv.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rapidcsv.h b/src/rapidcsv.h index d1adca6..bc83e0e 100644 --- a/src/rapidcsv.h +++ b/src/rapidcsv.h @@ -2,7 +2,7 @@ * rapidcsv.h * * URL: https://github.com/d99kris/rapidcsv - * Version: 8.94 + * Version: 8.95 * * Copyright (C) 2017-2026 Kristofer Berggren * All rights reserved.