CSV parser in C++

Back to Home

Introduction:

csvparser is a simple c++ program. It can load, save and modify Microsoft Excel friendly CSV files.

It supports quotation marks, line breaks and commas in cell values.

It uses std::map to store data. It supports up to 2^32 columns and rows.

About:

csvparser by Hamid Soltani. (gmail: hsoltanim)

Code:

Download csvparser.cpp

/****************************************************************************

Introduction:
csvdata is a simple c++ class.
It can load, save and modify Microsoft Excel friendly CSV files.
It supports quotation marks, line breaks and commas in cell values.
It uses std::map to store data. It supports up to 2^32 columns and rows.

Usage:

Create a csvdata:
csvdata csv;

Load a CSV file:
csv.LoadFile("filename.csv");

Keep current data and load a CSV file:
csv.LoadFile("filename.csv",false);

Save to a CSV file:
csv.SaveFile("filename.csv");

Assign a value to cell:
csv.SetCell(row, column, value);

Get cell value:
value = csv.GetCell(row, column);

Get cell value as a double:
check=GetCellDouble(row, column, &x)

Erase a cell:
csv.EraseCell(row, column); // same as SetCell(row, column, "");

Check if a cell has value and assign it to a string:
check = csv.Find(row, column, &str);

Get lower and upper bounds:
check = csv.LBElem(&row, &column, &str);
check = csv.UBElem(&row, &column, &str);

Search for a value from position row, column:
check = csv.Search(value, &row, &column);

Search for a value from position 0, 0:
check = csv.Search(value, &row, &column, true);

Iteration:
check = csv.BeginIter(&it);
check = csv.NextIter(&it);
csv.GetIter(it, &row, &column, &str);

Clear data:
csv.Clear();

() Operator (read and write):
csv(row, column)

Convert a string to a safe CSV string:
value = SafeStr(str);

Convert a CSV string to a primary form:
value = PrimaryStr(str);

csvdata version 1.3 by Hamid Soltani. (gmail: hsoltanim)
https://csvparser.github.io/
Last modified: Aug. 2016.

*****************************************************************************/

#include "stdafx.h"

#include < map >

#include < fstream >
#include < string >

// header files used by main() function
#include < iostream >
#include < stdlib.h >
#include < stdio.h >
#include < ctime >

using LI = unsigned long int;
using LLI = unsigned long long int;
union _I {
	LLI index;
	struct {
		LI column;
		LI row;
	} at;
};

/****************************************************************************/

class csvdata
{
private:
	std::map< LLI, std::string > csv_map;
	LLI _index(LI row, LI column);
	LI _row(LLI index);
	LI _column(LLI index);
public:
	csvdata();
	~csvdata();
	int LoadFile(const char* filename, bool isclear = true);
	int SaveFile(const char* filename);
	int EraseCell(LI row, LI column);
	int SetCell(LI row, LI column, const std::string& value);
	std::string GetCell(LI row, LI column);
	bool GetCellDouble(LI row, LI column, double& x);
	bool Search(const std::string& value, LI& row, LI& column, bool is_reset = false);
	bool Find(LI row, LI column, std::string& value);
	bool LBElem(LI& row, LI& column, std::string& value);
	bool UBElem(LI& row, LI& column, std::string& value);
	bool BeginIter(std::map< LLI, std::string >::iterator& it);
	bool NextIter(std::map< LLI, std::string >::iterator& it);
	void GetIter(std::map< LLI, std::string >::iterator& it, LI& row, LI& column, std::string& value);
	int Clear();
	std::string& operator() (const LI row, const LI column);
};

const std::string PrimaryStr(const std::string& s);
const std::string SafeStr(const std::string& s);
bool StrDouble(const std::string s, double& x);

using namespace std;

/****************************************************************************/

LLI csvdata::_index(LI row, LI column)
{
	_I i;
	i.at.row = row;
	i.at.column = column;
	return i.index;
}
LI csvdata::_row(LLI index)
{
	_I i;
	i.index = index;
	return i.at.row;
}
LI csvdata::_column(LLI index)
{
	_I i;
	i.index = index;
	return i.at.column;
}

csvdata::csvdata()
{

}

csvdata::~csvdata()
{
	Clear();
}

int csvdata::LoadFile(const char* filename, bool isclear)
{
	if (isclear)
		Clear();

	LI row = 0;
	LI column = 0;

	string cell = "";

	bool qflag = false;

	char c;

	ifstream is(filename);

	if (!is.good())
		return 1;

	while (is.get(c))
	{
		if (qflag)
		{
			if (c == '"')
			{
				if (is.peek() == '"')
				{
					is.get(c);
					cell += c;
				}
				else
					qflag = false;
			}
			else
				cell += c;
		}
		else
		{
			if ((c == '"') && (cell.length() == 0))
				qflag = true;
			else if (c == ',')
			{
				if (cell.length() > 0)
				{
					csv_map[_index(row, column)] = cell;
					cell = "";
				}
				column++;
			}
			else if (c == '\n')
			{
				if (cell.length() > 0)
				{
					csv_map[_index(row, column)] = cell;
					cell = "";
				}
				row++;
				column = 0;
			}
			else if (c >= 32)
				cell += c;
		}
	}
	if (cell.length() > 0)
		csv_map[_index(row, column)] = cell;

	is.close();
	return 0;
}

int csvdata::SaveFile(const char* filename)
{
	LI row = 0;
	LI column = 0;

	ofstream os(filename);

	if (!os.good())
	{
		return 1;
	}

	for (auto it : csv_map)
	{
		LLI ind = it.first;
		if (row < _row(ind))
		{
			while (row<_row(ind))
			{
				os << "\n";
				row++;
			}
			column = 0;
		}
		while (column < _column(ind))
		{
			os << ",";
			column++;
		}
		os << SafeStr(it.second).c_str();
	}

	os << "\n";
	os.close();
	return 0;
}

int csvdata::EraseCell(LI row, LI column)
{
	csv_map.erase(_index(row, column));
	return 0;
}

int csvdata::SetCell(LI row, LI column, const string& value)
{
	unsigned int len = (unsigned)value.length();
	if (len == 0)
	{
		csv_map.erase(_index(row, column));
		return 1;
	}
	else
	{
		csv_map[_index(row, column)] = value;
		return 0;
	}
}

string csvdata::GetCell(LI row, LI column)
{
	auto it = csv_map.find(_index(row, column));
	if (it != csv_map.end())
		return it->second;
	else
		return "";
}

bool csvdata::GetCellDouble(LI row, LI column, double& x)
{
	auto it = csv_map.find(_index(row, column));
	if (it != csv_map.end())
	{
		return StrDouble(it->second, x);
	}
	else
		return false;
}

bool csvdata::Search(const string& value, LI& row, LI& column, bool is_reset)
{
	if (is_reset)
	{
		row = 0;
		column = 0;
	}
	for (map< LLI, string >::iterator it = csv_map.lower_bound(_index(row, column)); it != csv_map.end(); ++it)
	{
		if (strcmp(it->second.c_str(), value.c_str()) == 0)
		{
			LLI ind = it->first;
			row = _row(ind);
			column = _column(ind);
			return true;
		}
	}
	return false;
}

bool csvdata::Find(LI row, LI column, string& value)
{
	map< LLI, string >::iterator it = csv_map.find(_index(row, column));
	if (it != csv_map.end())
	{
		value = it->second;
		return true;
	}
	else
	{
		return false;
	}
}

bool csvdata::LBElem(LI& row, LI& column, string& value)
{
	map< LLI, string >::iterator it = csv_map.lower_bound(_index(row, column));
	if (it != csv_map.end())
	{
		LLI ind = it->first;
		row = _row(ind);
		column = _column(ind);
		value = it->second;
		return true;
	}
	else
	{
		return false;
	}
}

bool csvdata::UBElem(LI& row, LI& column, string& value)
{
	map< LLI, string >::iterator it = csv_map.upper_bound(_index(row, column));
	if (it != csv_map.end())
	{
		LLI ind = it->first;
		row = _row(ind);
		column = _column(ind);
		value = it->second;
		return true;
	}
	else
	{
		return false;
	}
}

bool csvdata::BeginIter(map< LLI, string >::iterator& it)
{
	it = csv_map.begin();
	return (it != csv_map.end());
}

bool csvdata::NextIter(map< LLI, string >::iterator& it)
{
	it++;
	return (it != csv_map.end());
}

void csvdata::GetIter(map< LLI, string >::iterator& it, LI& row, LI& column, string& value)
{
	row = _row(it->first);
	column = _column(it->first);
	value = it->second;
}

int csvdata::Clear()
{
	csv_map.clear();
	return 0;
}

string& csvdata::operator() (const LI row, const LI column)
{
	return csv_map[_index(row, column)];
}

/****************************************************************************/

const string PrimaryStr(const string& s)
{
	string t;
	unsigned int len = (unsigned)s.length();
	if ((len>0) && (s[0] == '"') && (s[len - 1] == '"'))
		for (unsigned int i = 1; i < len - 1;i++)
		{
			t += s[i];
			if ((s[i] == '"') && (s[i + 1] == '"'))
				i++;
		}
	else
		t = s;
	return t;
}

const string SafeStr(const string& s)
{
	string t;
	unsigned int len = (unsigned)s.length();
	if ((s[0] == '"') && (s[len - 1] == '"'))
	{
		t = "\"";
		for (unsigned int i = 1; i < len - 1;i++)
			if (s[i] == '"')
			{
				t += "\"\"";
				if (s[i + 1] == '"')
					i++;
			}
			else
			{
				t += s[i];
			}
		t += "\"";
	}
	else
	{
		unsigned int i = 0;
		bool qneed = (s[0] == '\"');
		while ((!qneed) && (i < len))
		{
			qneed = ((s[i] == ',') || (s[i] == '\n'));
			i++;
		}
		if (qneed)
		{
			t = "\"";
			for (unsigned int i = 0; i < len;i++)
			{
				if (s[i] == '"')
					t += "\"\"";
				else
					t += s[i];
			}
			t += "\"";
		}
		else
		{
			t = s;
		}
	}
	return t;
}

bool StrDouble(const string s, double& x)
{
	double d;
	try {
		d = stod(s);
	}
	catch (const invalid_argument&) {
		return false;
	}
	catch (const out_of_range&) {
		return false;
	}
	x = d;
	return true;
}

// example of using csvdata class
int main()
{
	csvdata csv;

	// assigning some data

	csv.SetCell(0, 0, "Multiplication Table:");
	for (int i = 1;i < 10;i++)
		for (int j = 1;j < 10;j++)
			csv.SetCell(i, j, to_string(i*j));

	csv.SetCell(12, 0, "Some Tests:");

	csv.SetCell(13, 1, "Comma, Test 1");
	csv.SetCell(13, 2, ",Comma Test 2");
	csv.SetCell(13, 3, "\"Comma Test 3,\"");
	csv.SetCell(13, 4, "\"Comma, Test 4\"");
	csv.SetCell(13, 5, "\",Comma Test 5\"");
	csv.SetCell(13, 6, "Comma Test 6,");

	csv.SetCell(14, 1, "Qutation\" Test 1");
	csv.SetCell(14, 2, "\"Qutation Test 2");
	csv.SetCell(14, 3, "Qutation Test 3\"");
	csv.SetCell(14, 4, "\"Qutation\" Test 4\"");
	csv.SetCell(14, 5, "\"\"Qutation Test 5\"");
	csv.SetCell(14, 6, "\"Qutation Test 6\"\"");

	csv.SetCell(15, 1, "Line break\n Test 1");
	csv.SetCell(15, 2, "\nLine break Test 2");
	csv.SetCell(15, 3, "Line break Test 3\n");
	csv.SetCell(15, 4, "\"Line break\n Test 4\"");
	csv.SetCell(15, 5, "\"\nLine break Test 5\"");
	csv.SetCell(15, 6, "\"Line break Test 6\n\"");

	csv.SetCell(16, 1, "Old Value");
	csv.SetCell(16, 1, "New Value");

	csv.SetCell(16, 2, "To be Erased!");

	csv(20, 1) = "By operator!";

	//save and load files
	csv.SaveFile("1.csv");

	csv.Clear();

	csv.SetCell(10, 8, "Date:");
	csv.SetCell(18, 8, "Date:");

	csv.SaveFile("2.csv");

	csv.LoadFile("1.csv");
	csv.LoadFile("2.csv", false);

	csv.EraseCell(16, 2);

	// assign current date after cell "Date:"
	struct tm newtime;
	__time64_t long_time;
	char timebuf[40];
	_time64(&long_time);
	_localtime64_s(&newtime, &long_time);
	strftime(timebuf, 40, "%m/%d/%Y %H:%M:%S", &newtime);

	LI row = 0;
	LI column = 0;
	string value;
	while (csv.Search("Date:", row, column))
	{
		csv.SetCell(row, column + 1, timebuf);
		column++;
	}

	// display method 1
	row = 0;
	column = 0;
	while (csv.LBElem(row, column, value))
	{
		cout << row << ", " << column << ": " << value.c_str() << endl;
		column++;
	}

	// display method 2
	map< LLI, string >::iterator it;
	for (bool csvchk = csv.BeginIter(it);csvchk;csvchk = csv.NextIter(it))
	{
		csv.GetIter(it, row, column, value);
		cout << row << ", " << column << ": " << value.c_str() << endl;
	}

	csv.SaveFile("3.csv");

	puts("Press Enter to exit...\n");
	getchar();

	return 0;
}