C++ rapidcsv
1.unittest.h
#pragma once
#ifndef _MSC_VER
#include <unistd.h>
#else
#include <io.h>
#endif
#include <fstream>
#include <string>
#include <sstream>
#define ExpectEqual(t, a, b) ExpectEqualFun<t>(a, b, #a, #b, __FILE__, __LINE__)
#define ExpectTrue(a) ExpectTrueFun(a, #a, __FILE__, __LINE__)
#define ExpectException(expr, excp) \
do \
{ \
bool success = false; \
try \
{ \
expr; \
} \
catch (const excp&) \
{ \
success = true; \
} \
catch(const std::exception& ex) \
{ \
std::stringstream ss; \
ss << unittest::detail::FileName(__FILE__) << ":" << std::to_string(__LINE__); \
ss << " ExpectException failed: unexpected exception '" << typeid(ex).name(); \
ss << "' thrown." << std::endl; \
throw std::runtime_error(ss.str()); \
} \
\
if (!success) \
{ \
std::stringstream ss; \
ss << unittest::detail::FileName(__FILE__) << ":" << std::to_string(__LINE__); \
ss << " ExpectException failed: expected exception '" << #excp << "' not thrown."; \
ss << std::endl; \
throw std::runtime_error(ss.str()); \
} \
} \
while (0)
namespace unittest
{
namespace detail
{
inline std::string FileName(const std::string& pPath)
{
const std::size_t slash = pPath.rfind("/");
std::string name = (slash != std::string::npos) ? pPath.substr(slash + 1) : pPath;
return name;
}
}
inline std::string TempPath()
{
char name[] = "rapidcsvtest.XXXXXX";
#ifndef _MSC_VER
int fd = mkstemp(name);
close(fd);
#else
_mktemp_s(name, strlen(name) + 1);
#endif
return std::string(name);
}
inline void WriteFile(const std::string& pPath, const std::string& pData)
{
std::ofstream outfile;
outfile.open(pPath, std::ifstream::out | std::ifstream::binary);
outfile << pData;
outfile.close();
}
inline std::string ReadFile(const std::string& pPath)
{
std::ifstream infile;
infile.open(pPath, std::ifstream::in | std::ifstream::binary);
std::string data((std::istreambuf_iterator<char>(infile)), std::istreambuf_iterator<char>());
infile.close();
return data;
}
inline void DeleteFile(const std::string& pPath)
{
std::remove(pPath.c_str());
}
template<typename T>
inline void ExpectEqualFun(T pTest, T pRef, const std::string& testName,
const std::string& refName, const std::string& filePath, int lineNo)
{
if (pTest != pRef)
{
std::stringstream ss;
ss << detail::FileName(filePath) << ":" << std::to_string(lineNo);
ss << " ExpectEqual failed: " << testName << " != " << refName << std::endl;
ss << testName << " = '" << pTest << "'" << std::endl;
ss << refName << " = '" << pRef << "'" << std::endl;
throw std::runtime_error(ss.str());
}
}
inline void ExpectTrueFun(bool pTest, const std::string& testName, const std::string& filePath,
int lineNo)
{
if (!pTest)
{
std::stringstream ss;
ss << detail::FileName(filePath) << ":" << std::to_string((long long)lineNo);
ss << " ExpectTrue failed: " << testName << " == false" << std::endl;
throw std::runtime_error(ss.str());
}
}
}
2.rapidcsv.h
#pragma once
#include <algorithm>
#include <cassert>
#include <cmath>
#ifdef HAS_CODECVT
#include <codecvt>
#endif
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <typeinfo>
#include <vector>
using namespace std;
#if defined(_MSC_VER)
#include <BaseTsd.h>
typedef SSIZE_T ssize_t;
#endif
namespace rapidcsv
{
#if defined(_MSC_VER)
static const bool sPlatformHasCR = true;
#else
static const bool sPlatformHasCR = false;
#endif
/**
* @brief Datastructure holding parameters controlling how invalid numbers (including
* empty strings) should be handled.
*/
struct ConverterParams
{
/**
* @brief Constructor
* @param pHasDefaultConverter specifies if conversion of non-numerical strings shall be
* converted to a default numerical value, instead of causing
* an exception to be thrown (default).
* @param pDefaultFloat floating-point default value to represent invalid numbers.
* @param pDefaultInteger integer default value to represent invalid numbers.
*/
explicit ConverterParams(const bool pHasDefaultConverter = false,
const long double pDefaultFloat = std::numeric_limits<long double>::signaling_NaN(),
const long long pDefaultInteger = 0)
: mHasDefaultConverter(pHasDefaultConverter)
, mDefaultFloat(pDefaultFloat)
, mDefaultInteger(pDefaultInteger)
{
}
/**
* @brief specifies if conversion of non-numerical strings shall be converted to a default
* numerical value, instead of causing an exception to be thrown (default).
*/
bool mHasDefaultConverter;
/**
* @brief floating-point default value to represent invalid numbers.
*/
long double mDefaultFloat;
/**
* @brief integer default value to represent invalid numbers.
*/
long long mDefaultInteger;
};
/**
* @brief Exception thrown when attempting to access Document data in a datatype which
* is not supported by the Converter class.
*/
class no_converter : public std::exception
{
/**
* @brief Provides details about the exception
* @returns an explanatory string
*/
virtual const char* what() const throw()
{
return "unsupported conversion datatype";
}
};
/**
* @brief Class providing conversion to/from numerical datatypes and strings. Only
* intended for rapidcsv internal usage, but exposed externally to allow
* specialization for custom datatype conversions.
*/
template<typename T>
class Converter
{
public:
/**
* @brief Constructor
* @param pConverterParams specifies how conversion of non-numerical values to
* numerical datatype shall be handled.
*/
Converter(const ConverterParams& pConverterParams)
: mConverterParams(pConverterParams)
{
}
/**
* @brief Converts numerical value to string representation.
* @param pVal numerical value
* @param pStr output string
*/
void ToStr(const T& pVal, std::string& pStr) const
{
if (typeid(T) == typeid(int) ||
typeid(T) == typeid(long) ||
typeid(T) == typeid(long long) ||
typeid(T) == typeid(unsigned) ||
typeid(T) == typeid(unsigned long) ||
typeid(T) == typeid(unsigned long long) ||
typeid(T) == typeid(float) ||
typeid(T) == typeid(double) ||
typeid(T) == typeid(long double) ||
typeid(T) == typeid(char))
{
std::ostringstream out;
out << pVal;
pStr = out.str();
}
else
{
throw no_converter();
}
}
/**
* @brief Converts string holding a numerical value to numerical datatype representation.
* @param pVal numerical value
* @param pStr output string
*/
void ToVal(const std::string& pStr, T& pVal) const
{
try
{
if (typeid(T) == typeid(int))
{
pVal = static_cast<T>(std::stoi(pStr));
return;
}
else if (typeid(T) == typeid(long))
{
pVal = static_cast<T>(std::stol(pStr));
return;
}
else if (typeid(T) == typeid(long long))
{
pVal = static_cast<T>(std::stoll(pStr));
return;
}
else if (typeid(T) == typeid(unsigned))
{
pVal = static_cast<T>(std::stoul(pStr));
return;
}
else if (typeid(T) == typeid(unsigned long))
{
pVal = static_cast<T>(std::stoul(pStr));
return;
}
else if (typeid(T) == typeid(unsigned long long))
{
pVal = static_cast<T>(std::stoull(pStr));
return;
}
}
catch (...)
{
if (!mConverterParams.mHasDefaultConverter)
{
throw;
}
else
{
pVal = static_cast<T>(mConverterParams.mDefaultInteger);
return;
}
}
try
{
if (typeid(T) == typeid(float))
{
pVal = static_cast<T>(std::stof(pStr));
return;
}
else if (typeid(T) == typeid(double))
{
pVal = static_cast<T>(std::stod(pStr));
return;
}
else if (typeid(T) == typeid(long double))
{
pVal = static_cast<T>(std::stold(pStr));
return;
}
}
catch (...)
{
if (!mConverterParams.mHasDefaultConverter)
{
throw;
}
else
{
pVal = static_cast<T>(mConverterParams.mDefaultFloat);
return;
}
}
if (typeid(T) == typeid(char))
{
pVal = static_cast<T>(pStr[0]);
return;
}
else
{
throw no_converter();
}
}
private:
const ConverterParams& mConverterParams;
};
/**
* @brief Specialized implementation handling string to string conversion.
* @param pVal string
* @param pStr string
*/
template<>
inline void Converter<std::string>::ToStr(const std::string& pVal, std::string& pStr) const
{
pStr = pVal;
}
/**
* @brief Specialized implementation handling string to string conversion.
* @param pVal string
* @param pStr string
*/
template<>
inline void Converter<std::string>::ToVal(const std::string& pStr, std::string& pVal) const
{
pVal = pStr;
}
/**
* @brief Datastructure holding parameters controlling which row and column should be
* treated as labels.
*/
struct LabelParams
{
/**
* @brief Constructor
* @param pColumnNameIdx specifies the zero-based row index of the column labels, setting
* it to -1 prevents column lookup by label name, and gives access
* to all rows as document data.
* @param pRowNameIdx specifies the zero-based column index of the row labels, setting
* it to -1 prevents row lookup by label name, and gives access
* to all columns as document data.
*/
explicit LabelParams(const int pColumnNameIdx = 0, const int pRowNameIdx = 0)
: mColumnNameIdx(pColumnNameIdx)
, mRowNameIdx(pRowNameIdx)
{
}
/**
* @brief specifies the zero-based row index of the column labels.
*/
int mColumnNameIdx;
/**
* @brief specifies the zero-based column index of the row labels.
*/
int mRowNameIdx;
};
/**
* @brief Datastructure holding parameters controlling how the CSV data fields are separated.
*/
struct SeparatorParams
{
/**
* @brief Constructor
* @param pSeparator specifies the column separator (default ',').
* @param pHasCR specifies whether a new document (i.e. not an existing document read)
* should use CR/LF instead of only LF (default is to use standard
* behavior of underlying platforms - CR/LF for Win, and LF for others).
*/
explicit SeparatorParams(const char pSeparator = ',', const bool pHasCR = sPlatformHasCR)
: mSeparator(pSeparator)
, mHasCR(pHasCR)
{
}
/**
* @brief specifies the column separator.
*/
char mSeparator;
/**
* @brief specifies whether new documents should use CR/LF instead of LF.
*/
bool mHasCR;
};
/**
* @brief Class representing a CSV document.
*/
class Document
{
public:
/**
* @brief Constructor
* @param pPath specifies the path of an existing CSV-file to populate the Document
* data with.
* @param pLabelParams specifies which row and column should be treated as labels.
* @param pSeparatorParams specifies which field and row separators should be used.
* @param pConverterParams specifies how invalid numbers (including empty strings) should be
* handled.
*/
explicit Document(const std::string& pPath = std::string(),
const LabelParams& pLabelParams = LabelParams(),
const SeparatorParams& pSeparatorParams = SeparatorParams(),
const ConverterParams& pConverterParams = ConverterParams())
: mPath(pPath)
, mLabelParams(pLabelParams)
, mSeparatorParams(pSeparatorParams)
, mConverterParams(pConverterParams)
{
if (!mPath.empty())
{
ReadCsv();
}
}
/**
* @brief Constructor
* @param pStream specifies an input stream to read CSV data from.
* @param pLabelParams specifies which row and column should be treated as labels.
* @param pSeparatorParams specifies which field and row separators should be used.
* @param pConverterParams specifies how invalid numbers (including empty strings) should be
* handled.
*/
explicit Document(std::istream& pStream,
const LabelParams& pLabelParams = LabelParams(),
const SeparatorParams& pSeparatorParams = SeparatorParams(),
const ConverterParams& pConverterParams = ConverterParams())
: mPath()
, mLabelParams(pLabelParams)
, mSeparatorParams(pSeparatorParams)
, mConverterParams(pConverterParams)
{
ReadCsv(pStream);
}
/**
* @brief Copy constructor
* @param pDocument specifies the Document instance to copy.
*/
explicit Document(const Document& pDocument)
: mPath(pDocument.mPath)
, mLabelParams(pDocument.mLabelParams)
, mSeparatorParams(pDocument.mSeparatorParams)
, mConverterParams(pDocument.mConverterParams)
, mData(pDocument.mData)
, mColumnNames(pDocument.mColumnNames)
, mRowNames(pDocument.mRowNames)
{
}
/**
* @brief Read Document data from file.
* @param pPath specifies the path of an existing CSV-file to populate the Document
* data with.
*/
void Load(const std::string& pPath)
{
mPath = pPath;
ReadCsv();
}
/**
* @brief Write Document data to file.
* @param pPath optionally specifies the path where the CSV-file will be created
* (if not specified, the original path provided when creating or
* loading the Document data will be used).
*/
void Save(const std::string& pPath = std::string())
{
if (!pPath.empty())
{
mPath = pPath;
}
WriteCsv();
}
/**
* @brief Write Document data to stream.
* @param pStream specifies an output stream to write the data to.
*/
void Save(std::ostream& pStream)
{
WriteCsv(pStream);
}
/**
* @brief Get column by index.
* @param pColumnIdx zero-based column index.
* @returns vector of column data.
*/
template<typename T>
std::vector<T> GetColumn(const size_t pColumnIdx) const
{
const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1);
std::vector<T> column;
Converter<T> converter(mConverterParams);
for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
{
if (std::distance(mData.begin(), itRow) > mLabelParams.mColumnNameIdx)
{
T val;
converter.ToVal(itRow->at(columnIdx), val);
column.push_back(val);
}
}
return column;
}
/**
* @brief Get column by name.
* @param pColumnName column label name.
* @returns vector of column data.
*/
template<typename T>
std::vector<T> GetColumn(const std::string& pColumnName) const
{
const ssize_t columnIdx = GetColumnIdx(pColumnName);
if (columnIdx < 0)
{
throw std::out_of_range("column not found: " + pColumnName);
}
return GetColumn<T>(columnIdx);
}
/**
* @brief Set column by index.
* @param pColumnIdx zero-based column index.
* @param pColumn vector of column data.
*/
template<typename T>
void SetColumn(const size_t pColumnIdx, const std::vector<T>& pColumn)
{
const size_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1);
while (pColumn.size() + (mLabelParams.mColumnNameIdx + 1) > GetDataRowCount())
{
std::vector<std::string> row;
row.resize(GetDataColumnCount());
mData.push_back(row);
}
if ((columnIdx + 1) > GetDataColumnCount())
{
for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
{
itRow->resize(columnIdx + 1 + (mLabelParams.mRowNameIdx + 1));
}
}
Converter<T> converter(mConverterParams);
for (auto itRow = pColumn.begin(); itRow != pColumn.end(); ++itRow)
{
std::string str;
converter.ToStr(*itRow, str);
mData.at(std::distance(pColumn.begin(), itRow) + (mLabelParams.mColumnNameIdx + 1)).at(columnIdx) = str;
}
}
/**
* @brief Set column by name.
* @param pColumnName column label name.
* @param pColumn vector of column data.
*/
template<typename T>
void SetColumn(const std::string& pColumnName, const std::vector<T>& pColumn)
{
const ssize_t columnIdx = GetColumnIdx(pColumnName);
if (columnIdx < 0)
{
throw std::out_of_range("column not found: " + pColumnName);
}
SetColumn<T>(columnIdx, pColumn);
}
/**
* @brief Remove column by index.
* @param pColumnIdx zero-based column index.
*/
void RemoveColumn(const size_t pColumnIdx)
{
const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1);
for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
{
itRow->erase(itRow->begin() + columnIdx);
}
}
/**
* @brief Remove column by name.
* @param pColumnName column label name.
*/
void RemoveColumn(const std::string& pColumnName)
{
ssize_t columnIdx = GetColumnIdx(pColumnName);
if (columnIdx < 0)
{
throw std::out_of_range("column not found: " + pColumnName);
}
RemoveColumn(columnIdx);
}
/**
* @brief Get number of data columns.
* @returns column count.
*/
size_t GetColumnCount() const
{
return (mData.size() > 0) ? (mData.at(0).size() - (mLabelParams.mRowNameIdx + 1)) : 0;
}
/**
* @brief Get row by index.
* @param pRowIdx zero-based row index.
* @returns vector of row data.
*/
template<typename T>
std::vector<T> GetRow(const size_t pRowIdx) const
{
const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1);
std::vector<T> row;
Converter<T> converter(mConverterParams);
for (auto itCol = mData.at(rowIdx).begin(); itCol != mData.at(rowIdx).end(); ++itCol)
{
if (std::distance(mData.at(rowIdx).begin(), itCol) > mLabelParams.mRowNameIdx)
{
T val;
converter.ToVal(*itCol, val);
row.push_back(val);
}
}
return row;
}
/**
* @brief Get row by name.
* @param pRowName row label name.
* @returns vector of row data.
*/
template<typename T>
std::vector<T> GetRow(const std::string& pRowName) const
{
ssize_t rowIdx = GetRowIdx(pRowName);
if (rowIdx < 0)
{
throw std::out_of_range("row not found: " + pRowName);
}
return GetRow<T>(rowIdx);
}
/**
* @brief Set row by index.
* @param pRowIdx zero-based row index.
* @param pRow vector of row data.
*/
template<typename T>
void SetRow(const size_t pRowIdx, const std::vector<T>& pRow)
{
const size_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1);
while ((rowIdx + 1) > GetDataRowCount())
{
std::vector<std::string> row;
row.resize(GetDataColumnCount());
mData.push_back(row);
}
if (pRow.size() > GetDataColumnCount())
{
for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
{
itRow->resize(pRow.size() + (mLabelParams.mRowNameIdx + 1));
}
}
Converter<T> converter(mConverterParams);
for (auto itCol = pRow.begin(); itCol != pRow.end(); ++itCol)
{
std::string str;
converter.ToStr(*itCol, str);
mData.at(rowIdx).at(std::distance(pRow.begin(), itCol) + (mLabelParams.mRowNameIdx + 1)) = str;
}
}
/**
* @brief Set row by name.
* @param pRowName row label name.
* @param pRow vector of row data.
*/
template<typename T>
void SetRow(const std::string& pRowName, const std::vector<T>& pRow)
{
ssize_t rowIdx = GetRowIdx(pRowName);
if (rowIdx < 0)
{
throw std::out_of_range("row not found: " + pRowName);
}
return SetRow<T>(rowIdx, pRow);
}
/**
* @brief Remove row by index.
* @param pRowIdx zero-based row index.
*/
void RemoveRow(const size_t pRowIdx)
{
const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1);
mData.erase(mData.begin() + rowIdx);
}
/**
* @brief Remove row by name.
* @param pRowName row label name.
*/
void RemoveRow(const std::string& pRowName)
{
ssize_t rowIdx = GetRowIdx(pRowName);
if (rowIdx < 0)
{
throw std::out_of_range("row not found: " + pRowName);
}
RemoveRow(rowIdx);
}
/**
* @brief Get number of data rows.
* @returns row count.
*/
size_t GetRowCount() const
{
return mData.size() - (mLabelParams.mColumnNameIdx + 1);
}
/**
* @brief Get cell by index.
* @param pRowIdx zero-based row index.
* @param pColumnIdx zero-based column index.
* @returns cell data.
*/
template<typename T>
T GetCell(const size_t pColumnIdx, const size_t pRowIdx) const
{
const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1);
const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1);
T val;
Converter<T> converter(mConverterParams);
converter.ToVal(mData.at(rowIdx).at(columnIdx), val);
return val;
}
/**
* @brief Get cell by name.
* @param pColumnName column label name.
* @param pRowName row label name.
* @returns cell data.
*/
template<typename T>
T GetCell(const std::string& pColumnName, const std::string& pRowName) const
{
const ssize_t columnIdx = GetColumnIdx(pColumnName);
if (columnIdx < 0)
{
throw std::out_of_range("column not found: " + pColumnName);
}
const ssize_t rowIdx = GetRowIdx(pRowName);
if (rowIdx < 0)
{
throw std::out_of_range("row not found: " + pRowName);
}
return GetCell<T>(columnIdx, rowIdx);
}
/**
* @brief Set cell by index.
* @param pRowIdx zero-based row index.
* @param pColumnIdx zero-based column index.
* @param pCell cell data.
*/
template<typename T>
void SetCell(const size_t pColumnIdx, const size_t pRowIdx, const T& pCell)
{
const size_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1);
const size_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1);
while ((rowIdx + 1) > GetDataRowCount())
{
std::vector<std::string> row;
row.resize(GetDataColumnCount());
mData.push_back(row);
}
if ((columnIdx + 1) > GetDataColumnCount())
{
for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
{
itRow->resize(columnIdx + 1);
}
}
std::string str;
Converter<T> converter(mConverterParams);
converter.ToStr(pCell, str);
mData.at(rowIdx).at(columnIdx) = str;
}
/**
* @brief Set cell by name.
* @param pColumnName column label name.
* @param pRowName row label name.
* @param pCell cell data.
*/
template<typename T>
void SetCell(const std::string& pColumnName, const std::string& pRowName, const T& pCell)
{
const ssize_t columnIdx = GetColumnIdx(pColumnName);
if (columnIdx < 0)
{
throw std::out_of_range("column not found: " + pColumnName);
}
const ssize_t rowIdx = GetRowIdx(pRowName);
if (rowIdx < 0)
{
throw std::out_of_range("row not found: " + pRowName);
}
SetCell<T>(columnIdx, rowIdx, pCell);
}
/**
* @brief Get column name
* @param pColumnIdx zero-based column index.
* @returns column name.
*/
std::string GetColumnName(const ssize_t pColumnIdx)
{
const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1);
if (mLabelParams.mColumnNameIdx < 0)
{
throw std::out_of_range("column name row index < 0: " + std::to_string((long long)mLabelParams.mColumnNameIdx));
}
return mData.at(mLabelParams.mColumnNameIdx).at(columnIdx);
}
/**
* @brief Set column name
* @param pColumnIdx zero-based column index.
* @param pColumnName column name.
*/
void SetColumnName(size_t pColumnIdx, const std::string& pColumnName)
{
const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1);
mColumnNames[pColumnName] = columnIdx;
if (mLabelParams.mColumnNameIdx < 0)
{
throw std::out_of_range("column name row index < 0: " + std::to_string((long long)mLabelParams.mColumnNameIdx));
}
mData.at(mLabelParams.mColumnNameIdx).at(columnIdx) = pColumnName;
}
/**
* @brief Get column names
* @returns vector of column names.
*/
std::vector<std::string> GetColumnNames()
{
if (mLabelParams.mColumnNameIdx >= 0)
{
return std::vector<std::string>(mData.at(mLabelParams.mColumnNameIdx).begin() +
(mLabelParams.mRowNameIdx + 1),
mData.at(mLabelParams.mColumnNameIdx).end());
}
return std::vector<std::string>();
}
/**
* @brief Get row name
* @param pRowIdx zero-based column index.
* @returns row name.
*/
std::string GetRowName(const ssize_t pRowIdx)
{
const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1);
if (mLabelParams.mRowNameIdx < 0)
{
throw std::out_of_range("row name column index < 0: " + std::to_string((long long)mLabelParams.mRowNameIdx));
}
return mData.at(rowIdx).at(mLabelParams.mRowNameIdx);
}
/**
* @brief Set row name
* @param pRowIdx zero-based row index.
* @param pRowName row name.
*/
void SetRowName(size_t pRowIdx, const std::string& pRowName)
{
const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1);
mRowNames[pRowName] = rowIdx;
if (mLabelParams.mRowNameIdx < 0)
{
throw std::out_of_range("row name column index < 0: " + std::to_string((long long)mLabelParams.mRowNameIdx));
}
mData.at(rowIdx).at(mLabelParams.mRowNameIdx) = pRowName;
}
/**
* @brief Get row names
* @returns vector of row names.
*/
std::vector<std::string> GetRowNames()
{
std::vector<std::string> rownames;
if (mLabelParams.mRowNameIdx >= 0)
{
for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
{
if (std::distance(mData.begin(), itRow) > mLabelParams.mColumnNameIdx)
{
rownames.push_back(itRow->at(mLabelParams.mRowNameIdx));
}
}
}
return rownames;
}
private:
void ReadCsv()
{
std::ifstream stream;
stream.exceptions(std::ifstream::failbit | std::ifstream::badbit);
stream.open(mPath, std::ios::binary);
#ifdef HAS_CODECVT
stream.seekg(0, std::ios::end);
std::streamsize length = stream.tellg();
stream.seekg(0, std::ios::beg);
std::vector<char> bom(2, '\0');
if (length >= 2)
{
stream.read(bom.data(), 2);
}
static const std::vector<char> bomU16le = { '\xff', '\xfe' };
static const std::vector<char> bomU16be = { '\xfe', '\xff' };
if ((bom == bomU16le) || (bom == bomU16be))
{
mIsUtf16 = true;
mIsLE = (bom == bomU16le);
std::wifstream wstream;
wstream.exceptions(std::wifstream::failbit | std::wifstream::badbit);
wstream.open(mPath, std::ios::binary);
if (mIsLE)
{
wstream.imbue(std::locale(wstream.getloc(),
new std::codecvt_utf16<wchar_t, 0x10ffff,
static_cast<std::codecvt_mode>(std::consume_header | std::little_endian)>));
}
else
{
wstream.imbue(std::locale(wstream.getloc(),
new std::codecvt_utf16<wchar_t, 0x10ffff,
std::consume_header>));
}
std::wstringstream wss;
wss << wstream.rdbuf();
std::string utf8 = ToString(wss.str());
std::stringstream ss(utf8);
ReadCsv(ss);
}
else
#endif
{
stream.seekg(0, std::ios::beg);
ReadCsv(stream);
}
}
void ReadCsv(std::istream& pStream)
{
pStream.seekg(0, std::ios::end);
std::streamsize fileLength = pStream.tellg();
pStream.seekg(0, std::ios::beg);
const std::streamsize bufLength = 64 * 1024;
std::vector<char> buffer(bufLength);
std::vector<std::string> row;
std::string cell;
bool quoted = false;
int cr = 0;
int lf = 0;
while (fileLength > 0)
{
std::streamsize readLength = min(fileLength, bufLength);/**/
pStream.read(buffer.data(), readLength);
for (int i = 0; i < readLength; ++i)
{
if (buffer[i] == '"')
{
if (cell.empty() || cell[0] == '"')
{
quoted = !quoted;
}
cell += buffer[i];
}
else if (buffer[i] == mSeparatorParams.mSeparator)
{
if (!quoted)
{
row.push_back(cell);
cell.clear();
}
else
{
cell += buffer[i];
}
}
else if (buffer[i] == '\r')
{
++cr;
}
else if (buffer[i] == '\n')
{
++lf;
row.push_back(cell);
cell.clear();
mData.push_back(row);
row.clear();
quoted = false; // disallow line breaks in quoted string, by auto-unquote at linebreak
}
else
{
cell += buffer[i];
}
}
fileLength -= readLength;
}
// Handle last line without linebreak
if (!cell.empty() || !row.empty())
{
row.push_back(cell);
cell.clear();
mData.push_back(row);
row.clear();
}
// Assume CR/LF if at least half the linebreaks have CR
mSeparatorParams.mHasCR = (cr > (lf / 2));
// Set up column labels
if ((mLabelParams.mColumnNameIdx >= 0) &&
(mData.size() > 0))
{
int i = 0;
//vs2013及以上的版本可以用此方法
// for (auto& columnName : mData[mLabelParams.mColumnNameIdx])
// {
//mColumnNames[columnName] = i++;
// }
//vs2010版的c++有相容性問題,使用以下代碼
string columnName;
for (int k = 0; k < mData[mLabelParams.mColumnNameIdx].size(); k++)
{
columnName = mData[mLabelParams.mColumnNameIdx][k];
mColumnNames[columnName] = i++;
}
}
// Set up row labels
if ((mLabelParams.mRowNameIdx >= 0) &&
(static_cast<ssize_t>(mData.size()) >
(mLabelParams.mColumnNameIdx + 1)))
{
int i = 0;
//vs2013及以上的版本可以用此方法
//for (auto& dataRow : mData)
//{
// mRowNames[dataRow[mLabelParams.mRowNameIdx]] = i++;
//}
//vs2010版的c++有相容性問題,使用以下代碼
for (int k = 0; k < mData.size(); k++)
{
vector<string> dataRow;
for (int j = 0; j < mData[i].size(); j++)
dataRow.push_back(mData[k][j]);
mRowNames[dataRow[mLabelParams.mRowNameIdx]] = i++;
}
}
}
void WriteCsv() const
{
#ifdef HAS_CODECVT
if (mIsUtf16)
{
std::stringstream ss;
WriteCsv(ss);
std::string utf8 = ss.str();
std::wstring wstr = ToWString(utf8);
std::wofstream wstream;
wstream.exceptions(std::wofstream::failbit | std::wofstream::badbit);
wstream.open(mPath, std::ios::binary | std::ios::trunc);
if (mIsLE)
{
wstream.imbue(std::locale(wstream.getloc(),
new std::codecvt_utf16<wchar_t, 0x10ffff,
static_cast<std::codecvt_mode>(std::little_endian)>));
}
else
{
wstream.imbue(std::locale(wstream.getloc(),
new std::codecvt_utf16<wchar_t, 0x10ffff>));
}
wstream << (wchar_t) 0xfeff;
wstream << wstr;
}
else
#endif
{
std::ofstream stream;
stream.exceptions(std::ofstream::failbit | std::ofstream::badbit);
stream.open(mPath, std::ios::binary | std::ios::trunc);
WriteCsv(stream);
}
}
void WriteCsv(std::ostream& pStream) const
{
for (auto itr = mData.begin(); itr != mData.end(); ++itr)
{
for (auto itc = itr->begin(); itc != itr->end(); ++itc)
{
if ((std::string::npos == itc->find(mSeparatorParams.mSeparator)) ||
((itc->length() >= 2) && ((*itc)[0] == '\"') && ((*itc)[itc->length() - 1] == '\"')))
{
pStream << *itc;
}
else
{
pStream << '"' << *itc << '"';
}
if (std::distance(itc, itr->end()) > 1)
{
pStream << mSeparatorParams.mSeparator;
}
}
pStream << (mSeparatorParams.mHasCR ? "\r\n" : "\n");
}
}
ssize_t GetColumnIdx(const std::string& pColumnName) const
{
if (mLabelParams.mColumnNameIdx >= 0)
{
if (mColumnNames.find(pColumnName) != mColumnNames.end())
{
return mColumnNames.at(pColumnName) - (mLabelParams.mRowNameIdx + 1);
}
}
return -1;
}
ssize_t GetRowIdx(const std::string& pRowName) const
{
if (mLabelParams.mRowNameIdx >= 0)
{
if (mRowNames.find(pRowName) != mRowNames.end())
{
return mRowNames.at(pRowName) - (mLabelParams.mColumnNameIdx + 1);
}
}
return -1;
}
size_t GetDataRowCount() const
{
return mData.size();
}
size_t GetDataColumnCount() const
{
return (mData.size() > 0) ? mData.at(0).size() : 0;
}
#ifdef HAS_CODECVT
#if defined(_MSC_VER)
#pragma warning (disable: 4996)
#endif
static std::string ToString(const std::wstring& pWStr)
{
size_t len = std::wcstombs(nullptr, pWStr.c_str(), 0) + 1;
char* cstr = new char[len];
std::wcstombs(cstr, pWStr.c_str(), len);
std::string str(cstr);
delete[] cstr;
return str;
}
static std::wstring ToWString(const std::string& pStr)
{
size_t len = 1 + mbstowcs(nullptr, pStr.c_str(), 0);
wchar_t* wcstr = new wchar_t[len];
std::mbstowcs(wcstr, pStr.c_str(), len);
std::wstring wstr(wcstr);
delete[] wcstr;
return wstr;
}
#if defined(_MSC_VER)
#pragma warning (default: 4996)
#endif
#endif
private:
std::string mPath;
LabelParams mLabelParams;
SeparatorParams mSeparatorParams;
ConverterParams mConverterParams;
std::vector<std::vector<std::string> > mData;
std::map<std::string, size_t> mColumnNames;
std::map<std::string, size_t> mRowNames;
#ifdef HAS_CODECVT
bool mIsUtf16 = false;
bool mIsLE = false;
#endif
};
}
3.main.c 測試
#include "rapidcsv.h"
#include "unittest.h"
#include <iostream>
using namespace std;
const string temp_csv_path = "temp.csv";
const string history_output_string = "temp2.csv";
int create_save_history_data(void)
{
string datas = "title1,title2,title3,title4,title5";
int rv = 0;
unittest::WriteFile(temp_csv_path, datas);
string outpath = history_output_string;
try
{
// to file stream
rapidcsv::Document doc(temp_csv_path); //load data
std::ofstream ostream;
ostream.exceptions(std::ifstream::failbit | std::ifstream::badbit);
ostream.open(outpath, std::ios::out | std::ios::ate);//沒有則生成,有則清空
doc.Save(ostream);
ostream.close();
}
catch (const std::exception& ex) //出錯
{
std::cout << ex.what() << std::endl;
rv = 1;
}
return rv;
}
int sub_save_history_data(string datas)
{
int rv = 0;
unittest::WriteFile(temp_csv_path, datas);
string outpath = history_output_string;
try
{
// to file stream
rapidcsv::Document doc(temp_csv_path); //load data
std::ofstream ostream;
ostream.exceptions(std::ifstream::failbit | std::ifstream::badbit);
ostream.open(outpath, std::ios::out | std::ios::app);//二進制和追加方式
doc.Save(ostream);
ostream.close();
}
catch (const std::exception& ex) //出錯
{
std::cout << ex.what() << std::endl;
rv = 1;
}
return rv;
}
int save_history_result()
{
string datas1,datas2;
datas1 = "1,"+"2,"+"3,"+"4,"+"5";
datas1 = datas1 + "\n";
datas2 = "12,"+"22,"+"32,"+"42,"+"52";
datas2 = datas2 + "\n";
sub_save_history_data(datas1);
sub_save_history_data(datas2);
return 0;
}
int read_history_result(long unixtime)
{
rapidcsv::Document doc(history_output_string);
//查找縱軸标題為“title3”,橫軸标題為“12”的字元串,輸出為32
string volume = doc.GetCell<string>("title3", "12");
std::cout << volume << std::endl;
return 0;
}
int main()
{
create_csv_data();//生成csv檔案,标題為title1-5共5個
write_csv_data();//插入資料,共兩行,第一行為1,2,3,4,5 第二行為12,22,32,42,52
read_csv_result();//讀取查找資料
return 0;
}