Skip to content

Commit 3f3a56f

Browse files
committed
A CSV parser.
1 parent 76e96ec commit 3f3a56f

File tree

3 files changed

+187
-0
lines changed

3 files changed

+187
-0
lines changed

Utils/CSV/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../scripts/Makefile

Utils/CSV/csv.h

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*******************************************************************************
2+
The MIT License (MIT)
3+
4+
Copyright (c) 2016 Dmitry "Dima" Korolev <[email protected]>
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.
23+
*******************************************************************************/
24+
25+
#ifndef CURRENT_UTILS_CSV_CSV_H
26+
#define CURRENT_UTILS_CSV_CSV_H
27+
28+
#include "../../port.h"
29+
30+
#include <fstream>
31+
32+
#include "../../TypeSystem/struct.h"
33+
#include "../../Bricks/strings/strings.h"
34+
#include "../../Bricks/exception.h"
35+
36+
namespace current {
37+
38+
struct CSVException : Exception {
39+
using Exception::Exception;
40+
};
41+
42+
struct CSVFileNotFoundException : CSVException {
43+
using CSVException::CSVException;
44+
};
45+
46+
struct CSVFileFormatException : CSVException {
47+
using CSVException::CSVException;
48+
};
49+
50+
CURRENT_STRUCT_T(CSV) {
51+
CURRENT_FIELD(header, std::vector<std::string>);
52+
CURRENT_FIELD(data, std::vector<std::vector<T>>);
53+
static CSV<T> ReadFile(const std::string& filename) {
54+
CSV<T> csv;
55+
std::ifstream fi(filename);
56+
if (!fi.good()) {
57+
CURRENT_THROW(CSVFileNotFoundException("The CSV file `" + filename + "` could not be opened."));
58+
}
59+
std::string line;
60+
if (!std::getline(fi, line)) {
61+
CURRENT_THROW(CSVFileFormatException("The CSV file `" + filename + "` is empty."));
62+
}
63+
if (!line.empty() && line.back() == '\r') {
64+
line.resize(line.size() - 1u); // NOTE(dkorolev): This may look like a hack, but I'd rather stay safe.
65+
}
66+
csv.header = current::strings::Split(line, ',');
67+
if (csv.header.empty()) {
68+
CURRENT_THROW(CSVFileFormatException("The CSV file `" + filename + "` does not even contain the header."));
69+
}
70+
while (std::getline(fi, line)) {
71+
if (!line.empty() && line.back() == '\r') {
72+
line.resize(line.size() - 1u); // NOTE(dkorolev): This may look like a hack, but I'd rather stay safe.
73+
}
74+
std::vector<T> row;
75+
for (const auto& field : current::strings::Split(line, ',')) {
76+
row.push_back(current::FromString<T>(field));
77+
}
78+
if (row.size() != csv.header.size()) {
79+
CURRENT_THROW(CSVFileFormatException("Column number mismatch in CSV file `" + filename + "`."));
80+
}
81+
csv.data.emplace_back(std::move(row));
82+
}
83+
return csv;
84+
}
85+
};
86+
87+
} // namespace current
88+
89+
#endif // CURRENT_UTILS_CSV_CSV_H

Utils/CSV/test.cc

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*******************************************************************************
2+
The MIT License (MIT)
3+
4+
Copyright (c) 2016 Dmitry "Dima" Korolev <[email protected]>
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.
23+
*******************************************************************************/
24+
25+
#include "csv.h"
26+
27+
#include "../../Bricks/file/file.h"
28+
#include "../../Bricks/dflags/dflags.h"
29+
#include "../../TypeSystem/Serialization/json.h"
30+
#include "../../3rdparty/gtest/gtest-main-with-dflags.h"
31+
32+
DEFINE_string(csv_test_tmpdir, ".current", "Local path for the test to create temporary files in.");
33+
34+
TEST(CSV, Smoke) {
35+
current::FileSystem::MkDir(FLAGS_csv_test_tmpdir, current::FileSystem::MkDirParameters::Silent);
36+
const std::string fn = current::FileSystem::JoinPath(FLAGS_csv_test_tmpdir, "test.csv");
37+
const auto persistence_file_remover = current::FileSystem::ScopedRmFile(fn);
38+
39+
ASSERT_THROW(current::CSV<double>::ReadFile(fn), current::CSVFileNotFoundException);
40+
41+
{
42+
current::FileSystem::WriteStringToFile("", fn.c_str());
43+
ASSERT_THROW(current::CSV<double>::ReadFile(fn), current::CSVFileFormatException);
44+
current::FileSystem::WriteStringToFile("\n", fn.c_str());
45+
ASSERT_THROW(current::CSV<double>::ReadFile(fn), current::CSVFileFormatException);
46+
}
47+
48+
{
49+
current::FileSystem::WriteStringToFile("A,B,C\n1,2,3\n4,5,6\n7,8,9\n", fn.c_str());
50+
const auto csv = current::CSV<double>::ReadFile(fn);
51+
EXPECT_EQ("[\"A\",\"B\",\"C\"]", JSON(csv.header));
52+
EXPECT_EQ(3u, csv.data.size());
53+
EXPECT_EQ(3u, csv.data[0].size());
54+
EXPECT_EQ(3u, csv.data[1].size());
55+
EXPECT_EQ(3u, csv.data[2].size());
56+
EXPECT_EQ(1, csv.data[0][0]);
57+
EXPECT_EQ(2, csv.data[0][1]);
58+
EXPECT_EQ(3, csv.data[0][2]);
59+
EXPECT_EQ(4, csv.data[1][0]);
60+
EXPECT_EQ(5, csv.data[1][1]);
61+
EXPECT_EQ(6, csv.data[1][2]);
62+
EXPECT_EQ(7, csv.data[2][0]);
63+
EXPECT_EQ(8, csv.data[2][1]);
64+
EXPECT_EQ(9, csv.data[2][2]);
65+
}
66+
67+
{
68+
current::FileSystem::WriteStringToFile("no,data,in,this,file\n", fn.c_str());
69+
const auto csv = current::CSV<double>::ReadFile(fn);
70+
EXPECT_EQ("[\"no\",\"data\",\"in\",\"this\",\"file\"]", JSON(csv.header));
71+
EXPECT_EQ(0u, csv.data.size());
72+
}
73+
74+
{
75+
current::FileSystem::WriteStringToFile("wrong,number,of,columns\n1,2,3\n", fn.c_str());
76+
ASSERT_THROW(current::CSV<double>::ReadFile(fn), current::CSVFileFormatException);
77+
}
78+
79+
{
80+
current::FileSystem::WriteStringToFile("X\r\n 1.0 \r\n 20e-1 \r\n \t\t3\t\t \n", fn.c_str());
81+
const auto csv = current::CSV<double>::ReadFile(fn);
82+
EXPECT_EQ("[\"X\"]", JSON(csv.header));
83+
EXPECT_EQ(3u, csv.data.size());
84+
EXPECT_EQ(1u, csv.data[0].size());
85+
EXPECT_EQ(1u, csv.data[1].size());
86+
EXPECT_EQ(1u, csv.data[2].size());
87+
EXPECT_EQ(1, csv.data[0][0]);
88+
EXPECT_EQ(2, csv.data[1][0]);
89+
EXPECT_EQ(3, csv.data[2][0]);
90+
}
91+
92+
{
93+
current::FileSystem::WriteStringToFile("just,a,header,with,no,newline,is,ok", fn.c_str());
94+
const auto csv = current::CSV<double>::ReadFile(fn);
95+
EXPECT_EQ("[\"just\",\"a\",\"header\",\"with\",\"no\",\"newline\",\"is\",\"ok\"]", JSON(csv.header));
96+
}
97+
}

0 commit comments

Comments
 (0)