I've been working through the fifth edition of Lajoie's C++ Primer and decided to write a CSV reader/writer as my first project. Its main features are:
- Able to explicitly call for:
- Individual/multiple rows by index and range
- Individual/multiple columns by name and index
- Able to write new rows using a
map<string,string>
formatted<column, value>
.
The code is available on Github and pasted below. Call me out for poor practices, bad design choices, whatever itches you. It's my first dive into C++ and I want to know how to improve.
main.cpp
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include "CSVFile.hpp"
int main(int argc, const char * argv[]) {
string file_name = "Sample.csv";
CSVFile csv_obj(file_name);
typedef vector<vector<string>> CsvVector;
vector<string> headers = csv_obj.get_header(); // Get available headers in passed CSV
CsvVector column_one = csv_obj.get_column("Column 1");
vector<string> row_1 = csv_obj.get_row(1); // Get rows by index
CsvVector row_1_to_3 = csv_obj.get_row(0, 3); // Get rows by range
map<string,string> row_map; // Map formatted Column: Column value
row_map["Column 1"] = "ColumnOneVal";
row_map["Column 2"] = "ColumnTwoVal";
row_map["Column 3"] = "ColumnThreeVal";
cout << endl;
csv_obj.write_row(row_map); // Use row_map to write a new row
return 0;
}
CSVFile.hpp
#ifndef CSVFile_hpp
#define CSVFile_hpp
#include <string>
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
using namespace std;
;class CSVFile {
typedef vector<vector<string>> CsvVector;
friend ostream& print(ostream&, const CSVFile&);
public:
CSVFile() = default;
CSVFile(const string&);
inline vector<string>::size_type get_size();
inline void headers();
inline vector<string> get_header();
CsvVector get_column(const int&);
CsvVector get_column(const vector<int>&);
CsvVector get_column(const string&);
CsvVector get_column(const vector<string>&);
vector<string> get_row(const int&);
CsvVector get_row(const int&, const int&); // Rows in range
// Planned: Explicitly declare line to write to.
// Pass vector of strings to write lines for
void write_row(const map<string,string>&);
inline CsvVector rows();
private:
vector<string> header;
CsvVector contents;
ifstream stream_read;
ofstream stream_write;
inline string remove_extension(const string&);
vector<string> column_info(const string&);
string merge_row_vector(const vector<string>&);
int index_from_string(const string&);
map<int,string> index_from_string(const map<string,string>& row_map);
};
ostream& print(ostream&, const CSVFile&);
void CSVFile::headers() {
for (auto&& i : header)
cout << i << ", ";
}
vector<string>::size_type CSVFile::get_size() {
return contents.size();
}
string CSVFile::remove_extension(const string& s) {
string ret = s;
ret.erase(ret.find_last_of("."), string::npos);
return ret;
}
vector<string> CSVFile::get_header() {
return header;
}
CSVFile::CsvVector CSVFile::rows() {
return contents;
}
#endif /* CSVFile_hpp */
CSVFile.cpp
#include "CSVFile.hpp"
#include <fstream>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <array>
// file doc: http://www.cplusplus.com/doc/tutorial/files/
// stream_readtream constructor: http://www.cplusplus.com/forum/general/17771/#msg89650
CSVFile::CSVFile(const string& file_name) {
stream_read.open(file_name, ios::binary);
string out_file = remove_extension(file_name) + "_OUT.csv";
stream_write.open(out_file, ios::binary);
stream_write << stream_read.rdbuf(); // Copy read file into output file
bool line_is_header = true; // First line assumed to be the header
stream_read.seekg(0); // "Rewind" infile stream
while (stream_read) {
// Grab whole row
string s;
if (!getline(stream_read, s))
break;
istringstream ss (s);
vector<string> record;
while(ss) {
// Split string by comma delimiter
string s;
if (!getline(ss, s, ','))
break;
record.push_back(s);
}
if (line_is_header)
header = record;
else
contents.push_back(record);
line_is_header = false; // First iteration clears through the header
}
}
void CSVFile::write_row(const map<string,string>& row_map) {
vector<string> row_vec(header.size(), "NULL");
map<int,string> index_map = index_from_string(row_map);
for(auto it = index_map.begin(); it != index_map.end(); ++it) {
row_vec[it->first] = it->second;
}
// for(auto&& i : row_vec) { cout << i << endl; }
string v_string = merge_row_vector(row_vec);
stream_write << endl << v_string;
}
map<int,string> CSVFile::index_from_string(const map<string,string>& row_map) {
map<int,string> index_map;
for(auto it = row_map.begin(); it != row_map.end(); ++it) {
index_map[index_from_string(it->first)] = it->second;
}
return index_map;
}
int CSVFile::index_from_string(const string& s) {
// Given column string, returns index of column
int pos = find(header.begin(), header.end(), s) - header.begin();
if (pos == header.size()) {
throw out_of_range("\nColumn: " + s + " not found in header");
}
return pos;
}
// http://stackoverflow.com/questions/1430757/c-vector-to-string
string CSVFile::merge_row_vector(const vector<string>& row_vec) {
stringstream sstream;
for(vector<string>::size_type ctr = 0; ctr < row_vec.size(); ++ctr) {
string current = row_vec[ctr];
// cout << "called : " << current << endl;
if (ctr == 0)
sstream << current << ",";
else if (current == "NULL")
sstream << ",";
else if (ctr == row_vec.size() - 1)
sstream << current;
else
sstream << current << ",";
}
return sstream.str();
}
vector<string> CSVFile::column_info(const string& s) {
// Returns all data in the passed column(s)
// Need to overload this function later
vector<string> data;
int index = index_from_string(s);
for (auto&& i : contents) {
data.push_back(i[index]);
}
return data;
}
CSVFile::CsvVector CSVFile::get_column(const int& index) {
CSVFile::CsvVector column_data;
vector<string> buffer;
for(unsigned i = 0; i < contents.size(); ++i) {
buffer.push_back(contents[i][index]);
}
column_data.push_back(buffer);
return column_data;
}
CSVFile::CsvVector CSVFile::get_column(const vector<int>& indices) {
CSVFile::CsvVector column_data;
for(auto&& index : indices)
{
vector<string> buffer;
for(unsigned i = 0; i < contents.size(); ++i)
{
buffer.push_back(contents[i][index]);
}
column_data.push_back(buffer);
}
return column_data;
}
CSVFile::CsvVector CSVFile::get_column(const string& s) {
CSVFile::CsvVector column_data;
column_data.push_back(column_info(s));
return column_data;
}
CSVFile::CsvVector CSVFile::get_column(const vector<string>& col_vec) {
CSVFile::CsvVector column_data;
for (auto&& column : col_vec) {
column_data.push_back(column_info(column));
}
return column_data;
}
vector<string> CSVFile::get_row(const int& i) {
return contents[i];
}
CSVFile::CsvVector CSVFile::get_row(const int& start, const int& end) {
CsvVector row_range;
for(unsigned i = start; i != end; ++i) {
row_range.push_back(contents[i]);
}
return row_range;
}
ostream& print(ostream& os, const CSVFile& csv) {
string csv_string;
for (auto&& i : csv.contents) {
stringstream s;
for (auto&& j : i) {
s << j << ' ';
}
s << "\n";
csv_string += s.str();
}
os << csv_string;
return os;
}