aboutsummaryrefslogtreecommitdiff
path: root/CSV Editor/src
diff options
context:
space:
mode:
Diffstat (limited to 'CSV Editor/src')
-rw-r--r--CSV Editor/src/main.cpp277
-rw-r--r--CSV Editor/src/rapidcsv.h1916
2 files changed, 2193 insertions, 0 deletions
diff --git a/CSV Editor/src/main.cpp b/CSV Editor/src/main.cpp
new file mode 100644
index 0000000..d8cd2d5
--- /dev/null
+++ b/CSV Editor/src/main.cpp
@@ -0,0 +1,277 @@
+// Standalone CSV Editor using ImGui + DirectX 9
+
+#include "imgui.h"
+#include "imgui_impl_dx9.h"
+#include "imgui_impl_win32.h"
+#include <d3d9.h>
+#include <tchar.h>
+#include <iostream>
+#include <vector>
+#include "rapidcsv.h"
+
+// Structs
+struct Cell {
+ std::string uniqueId;
+ std::string value;
+};
+
+// Data
+static LPDIRECT3D9 g_pD3D = nullptr;
+static LPDIRECT3DDEVICE9 g_pd3dDevice = nullptr;
+static UINT g_ResizeWidth = 0, g_ResizeHeight = 0;
+static D3DPRESENT_PARAMETERS g_d3dpp = {};
+
+//member function
+
+std::vector<std::string> getCSVHeader();
+std::vector<std::vector<Cell>> getCSVData();
+
+// Forward declarations of helper functions
+bool CreateDeviceD3D(HWND hWnd);
+void CleanupDeviceD3D();
+void ResetDevice();
+LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+// Main code
+int main(int, char**)
+{
+ // Create application window
+ //ImGui_ImplWin32_EnableDpiAwareness();
+ std::vector<std::string> headers = getCSVHeader();
+ std::vector<std::vector<Cell>> data = getCSVData();
+
+ WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr, L"ImGui Example", nullptr };
+ ::RegisterClassExW(&wc);
+ HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX9 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, nullptr, nullptr, wc.hInstance, nullptr);
+
+ // Initialize Direct3D
+ if (!CreateDeviceD3D(hwnd))
+ {
+ CleanupDeviceD3D();
+ ::UnregisterClassW(wc.lpszClassName, wc.hInstance);
+ return 1;
+ }
+
+ // Show the window
+ ::ShowWindow(hwnd, SW_SHOWDEFAULT);
+ ::UpdateWindow(hwnd);
+
+ // Setup Dear ImGui context
+ IMGUI_CHECKVERSION();
+ ImGui::CreateContext();
+ ImGuiIO& io = ImGui::GetIO(); (void)io;
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
+
+ // Setup Dear ImGui style
+ ImGui::StyleColorsDark();
+ //ImGui::StyleColorsLight();
+
+ // Setup Platform/Renderer backends
+ ImGui_ImplWin32_Init(hwnd);
+ ImGui_ImplDX9_Init(g_pd3dDevice);
+
+
+ ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
+
+ // Main loop
+ bool done = false;
+ while (!done)
+ {
+ // Poll and handle messages (inputs, window resize, etc.)
+ // See the WndProc() function below for our to dispatch events to the Win32 backend.
+ MSG msg;
+ while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE))
+ {
+ ::TranslateMessage(&msg);
+ ::DispatchMessage(&msg);
+ if (msg.message == WM_QUIT)
+ done = true;
+ }
+ if (done)
+ break;
+
+ // Handle window resize (we don't resize directly in the WM_SIZE handler)
+ if (g_ResizeWidth != 0 && g_ResizeHeight != 0)
+ {
+ g_d3dpp.BackBufferWidth = g_ResizeWidth;
+ g_d3dpp.BackBufferHeight = g_ResizeHeight;
+ g_ResizeWidth = g_ResizeHeight = 0;
+ ResetDevice();
+ }
+
+ // Start the Dear ImGui frame
+ ImGui_ImplDX9_NewFrame();
+ ImGui_ImplWin32_NewFrame();
+ ImGui::NewFrame();
+
+ ImGui::SetNextWindowPos(ImGui::GetMainViewport()->Pos);
+ ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size);
+ static ImGuiTableFlags flags = ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable;
+ if (ImGui::Begin("CSV Editor")) {
+ if (ImGui::BeginTable("table1", headers.size(), flags))
+ {
+ for (const auto& header : headers) {
+ //std::cout << header << std::endl;
+ ImGui::TableSetupColumn(header.c_str(), ImGuiTableColumnFlags_WidthFixed);
+ }
+ ImGui::TableHeadersRow();
+ for (size_t i = 0; i < data.size(); i++) {
+ ImGui::TableNextRow();
+ for (size_t j = 0; j < data[i].size(); j++) {
+ //ImGui::TableNextColumn(); ImGui::Text(data[i][j].value.c_str());
+ ImGui::TableSetColumnIndex(j);
+ ImGui::PushID(data[i][j].uniqueId.c_str());
+ //char value[] = data[i][j].value.c_str();
+ char* value = &data[i][j].value[0];
+ //ImGui::InputText("Rdi", &data[i][j].value, Im);
+ //std::string value = data[i][j].value;
+ ImGui::InputText("##image_path", value, data[i][j].value.size()*100, ImGuiInputTextFlags_None);
+ ImGui::PopID();
+ }
+ }
+
+ /*for (int row = 0; row < 3; row++)
+ {
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn(); ImGui::Text("Oh dear");
+ ImGui::TableNextColumn(); ImGui::Text("Oh dear");
+ ImGui::TableNextColumn(); ImGui::Text("Oh dear");
+ }*/
+ ImGui::EndTable();
+ }
+ }
+ ImGui::End();
+
+
+
+ // Rendering
+ ImGui::EndFrame();
+ g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE);
+ g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
+ g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE);
+ D3DCOLOR clear_col_dx = D3DCOLOR_RGBA((int)(clear_color.x * clear_color.w * 255.0f), (int)(clear_color.y * clear_color.w * 255.0f), (int)(clear_color.z * clear_color.w * 255.0f), (int)(clear_color.w * 255.0f));
+ g_pd3dDevice->Clear(0, nullptr, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, clear_col_dx, 1.0f, 0);
+ if (g_pd3dDevice->BeginScene() >= 0)
+ {
+ ImGui::Render();
+ ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData());
+ g_pd3dDevice->EndScene();
+ }
+ HRESULT result = g_pd3dDevice->Present(nullptr, nullptr, nullptr, nullptr);
+
+ // Handle loss of D3D9 device
+ if (result == D3DERR_DEVICELOST && g_pd3dDevice->TestCooperativeLevel() == D3DERR_DEVICENOTRESET)
+ ResetDevice();
+ }
+
+ ImGui_ImplDX9_Shutdown();
+ ImGui_ImplWin32_Shutdown();
+ ImGui::DestroyContext();
+
+ CleanupDeviceD3D();
+ ::DestroyWindow(hwnd);
+ ::UnregisterClassW(wc.lpszClassName, wc.hInstance);
+
+ return 0;
+}
+
+std::vector<std::string> getCSVHeader() {
+
+ rapidcsv::Document doc("simple.csv");
+ // Get headers
+ std::vector<std::string> headers = doc.GetColumnNames();
+ // Print headers
+ for (const auto& header : headers) {
+ std::cout << header << std::endl;
+ }
+ return headers;
+}
+
+std::vector<std::vector<Cell>> getCSVData() {
+ rapidcsv::Document doc("simple.csv");
+ std::vector<std::vector<Cell>> values;
+
+ for (size_t row = 0; row < doc.GetRowCount(); row++) {
+ std::vector<Cell> rowValues;
+
+ for (size_t col = 0; col < doc.GetColumnCount(); col++) {
+ Cell cell;
+ cell.uniqueId = std::to_string(row) + "-" + std::to_string(col);
+ cell.value = doc.GetCell<std::string>(col, row);
+ rowValues.push_back(cell);
+ }
+ values.push_back(rowValues);
+ }
+
+ return values;
+}
+
+// Helper functions
+
+bool CreateDeviceD3D(HWND hWnd)
+{
+ if ((g_pD3D = Direct3DCreate9(D3D_SDK_VERSION)) == nullptr)
+ return false;
+
+ // Create the D3DDevice
+ ZeroMemory(&g_d3dpp, sizeof(g_d3dpp));
+ g_d3dpp.Windowed = TRUE;
+ g_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
+ g_d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; // Need to use an explicit format with alpha if needing per-pixel alpha composition.
+ g_d3dpp.EnableAutoDepthStencil = TRUE;
+ g_d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
+ g_d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; // Present with vsync
+ //g_d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; // Present without vsync, maximum unthrottled framerate
+ if (g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &g_d3dpp, &g_pd3dDevice) < 0)
+ return false;
+
+ return true;
+}
+
+void CleanupDeviceD3D()
+{
+ if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = nullptr; }
+ if (g_pD3D) { g_pD3D->Release(); g_pD3D = nullptr; }
+}
+
+void ResetDevice()
+{
+ ImGui_ImplDX9_InvalidateDeviceObjects();
+ HRESULT hr = g_pd3dDevice->Reset(&g_d3dpp);
+ if (hr == D3DERR_INVALIDCALL)
+ IM_ASSERT(0);
+ ImGui_ImplDX9_CreateDeviceObjects();
+}
+
+// Forward declare message handler from imgui_impl_win32.cpp
+extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+// Win32 message handler
+// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
+// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
+// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
+// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
+LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
+ return true;
+
+ switch (msg)
+ {
+ case WM_SIZE:
+ if (wParam == SIZE_MINIMIZED)
+ return 0;
+ g_ResizeWidth = (UINT)LOWORD(lParam); // Queue resize
+ g_ResizeHeight = (UINT)HIWORD(lParam);
+ return 0;
+ case WM_SYSCOMMAND:
+ if ((wParam & 0xfff0) == SC_KEYMENU) // Disable ALT application menu
+ return 0;
+ break;
+ case WM_DESTROY:
+ ::PostQuitMessage(0);
+ return 0;
+ }
+ return ::DefWindowProcW(hWnd, msg, wParam, lParam);
+}
diff --git a/CSV Editor/src/rapidcsv.h b/CSV Editor/src/rapidcsv.h
new file mode 100644
index 0000000..280aba3
--- /dev/null
+++ b/CSV Editor/src/rapidcsv.h
@@ -0,0 +1,1916 @@
+/*
+ * rapidcsv.h
+ *
+ * URL: https://github.com/d99kris/rapidcsv
+ * Version: 8.83
+ *
+ * Copyright (C) 2017-2024 Kristofer Berggren
+ * All rights reserved.
+ *
+ * rapidcsv is distributed under the BSD 3-Clause license, see LICENSE for details.
+ *
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#ifdef HAS_CODECVT
+#include <codecvt>
+#include <locale>
+#endif
+#include <fstream>
+#include <functional>
+#include <iomanip>
+#include <iostream>
+#include <limits>
+#include <map>
+#include <sstream>
+#include <string>
+#include <typeinfo>
+#include <vector>
+
+namespace rapidcsv
+{
+#if defined(_MSC_VER)
+ static const bool sPlatformHasCR = true;
+#else
+ static const bool sPlatformHasCR = false;
+#endif
+ static const std::vector<char> s_Utf8BOM = { '\xef', '\xbb', '\xbf' };
+
+ /**
+ * @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.
+ * @param pNumericLocale specifies whether to honor LC_NUMERIC locale (default
+ * true).
+ */
+ explicit ConverterParams(const bool pHasDefaultConverter = false,
+ const long double pDefaultFloat = std::numeric_limits<long double>::signaling_NaN(),
+ const long long pDefaultInteger = 0,
+ const bool pNumericLocale = true)
+ : mHasDefaultConverter(pHasDefaultConverter)
+ , mDefaultFloat(pDefaultFloat)
+ , mDefaultInteger(pDefaultInteger)
+ , mNumericLocale(pNumericLocale)
+ {
+ }
+
+ /**
+ * @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 specifies whether to honor LC_NUMERIC locale.
+ */
+ bool mNumericLocale;
+ };
+
+ /**
+ * @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
+ {
+ public:
+ /**
+ * @brief Provides details about the exception
+ * @returns an explanatory string
+ */
+ const char* what() const throw() override
+ {
+ 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(long double) ||
+ typeid(T) == typeid(char))
+ {
+ std::ostringstream out;
+ out << pVal;
+ pStr = out.str();
+ }
+ else if (typeid(T) == typeid(float))
+ {
+ std::ostringstream out;
+ out << std::setprecision(9) << pVal;
+ pStr = out.str();
+ }
+ else if (typeid(T) == typeid(double))
+ {
+ std::ostringstream out;
+ out << std::setprecision(17) << 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 (mConverterParams.mNumericLocale)
+ {
+ 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;
+ }
+ }
+ else
+ {
+ if ((typeid(T) == typeid(float)) ||
+ (typeid(T) == typeid(double)) ||
+ (typeid(T) == typeid(long double)))
+ {
+ std::istringstream iss(pStr);
+ iss.imbue(std::locale::classic());
+ iss >> pVal;
+ if (iss.fail() || iss.bad() || !iss.eof())
+ {
+ throw std::invalid_argument("istringstream: no conversion");
+ }
+ 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;
+ }
+
+ template<typename T>
+ using ConvFunc = std::function<void (const std::string & pStr, T & pVal)>;
+
+ /**
+ * @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. Default: 0
+ * @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. Default: -1
+ */
+ explicit LabelParams(const int pColumnNameIdx = 0, const int pRowNameIdx = -1)
+ : mColumnNameIdx(pColumnNameIdx)
+ , mRowNameIdx(pRowNameIdx)
+ {
+ if (mColumnNameIdx < -1)
+ {
+ const std::string errStr = "invalid column name index " +
+ std::to_string(mColumnNameIdx) + " < -1";
+ throw std::out_of_range(errStr);
+ }
+
+ if (mRowNameIdx < -1)
+ {
+ const std::string errStr = "invalid row name index " +
+ std::to_string(mRowNameIdx) + " < -1";
+ throw std::out_of_range(errStr);
+ }
+ }
+
+ /**
+ * @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 pTrim specifies whether to trim leading and trailing spaces from
+ * cells read (default false).
+ * @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).
+ * @param pQuotedLinebreaks specifies whether to allow line breaks in quoted text (default false)
+ * @param pAutoQuote specifies whether to automatically dequote data during read, and add
+ * quotes during write (default true).
+ * @param pQuoteChar specifies the quote character (default '\"').
+ */
+ explicit SeparatorParams(const char pSeparator = ',', const bool pTrim = false,
+ const bool pHasCR = sPlatformHasCR, const bool pQuotedLinebreaks = false,
+ const bool pAutoQuote = true, const char pQuoteChar = '"')
+ : mSeparator(pSeparator)
+ , mTrim(pTrim)
+ , mHasCR(pHasCR)
+ , mQuotedLinebreaks(pQuotedLinebreaks)
+ , mAutoQuote(pAutoQuote)
+ , mQuoteChar(pQuoteChar)
+ {
+ }
+
+ /**
+ * @brief specifies the column separator.
+ */
+ char mSeparator;
+
+ /**
+ * @brief specifies whether to trim leading and trailing spaces from cells read.
+ */
+ bool mTrim;
+
+ /**
+ * @brief specifies whether new documents should use CR/LF instead of LF.
+ */
+ bool mHasCR;
+
+ /**
+ * @brief specifies whether to allow line breaks in quoted text.
+ */
+ bool mQuotedLinebreaks;
+
+ /**
+ * @brief specifies whether to automatically dequote cell data.
+ */
+ bool mAutoQuote;
+
+ /**
+ * @brief specifies the quote character.
+ */
+ char mQuoteChar;
+ };
+
+ /**
+ * @brief Datastructure holding parameters controlling how special line formats should be
+ * treated.
+ */
+ struct LineReaderParams
+ {
+ /**
+ * @brief Constructor
+ * @param pSkipCommentLines specifies whether to skip lines prefixed with
+ * mCommentPrefix. Default: false
+ * @param pCommentPrefix specifies which prefix character to indicate a comment
+ * line. Default: #
+ * @param pSkipEmptyLines specifies whether to skip empty lines. Default: false
+ */
+ explicit LineReaderParams(const bool pSkipCommentLines = false,
+ const char pCommentPrefix = '#',
+ const bool pSkipEmptyLines = false)
+ : mSkipCommentLines(pSkipCommentLines)
+ , mCommentPrefix(pCommentPrefix)
+ , mSkipEmptyLines(pSkipEmptyLines)
+ {
+ }
+
+ /**
+ * @brief specifies whether to skip lines prefixed with mCommentPrefix.
+ */
+ bool mSkipCommentLines;
+
+ /**
+ * @brief specifies which prefix character to indicate a comment line.
+ */
+ char mCommentPrefix;
+
+ /**
+ * @brief specifies whether to skip empty lines.
+ */
+ bool mSkipEmptyLines;
+ };
+
+ /**
+ * @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.
+ * @param pLineReaderParams specifies how special line formats should be treated.
+ */
+ explicit Document(const std::string& pPath = std::string(),
+ const LabelParams& pLabelParams = LabelParams(),
+ const SeparatorParams& pSeparatorParams = SeparatorParams(),
+ const ConverterParams& pConverterParams = ConverterParams(),
+ const LineReaderParams& pLineReaderParams = LineReaderParams())
+ : mPath(pPath)
+ , mLabelParams(pLabelParams)
+ , mSeparatorParams(pSeparatorParams)
+ , mConverterParams(pConverterParams)
+ , mLineReaderParams(pLineReaderParams)
+ , mData()
+ , mColumnNames()
+ , mRowNames()
+ {
+ if (!mPath.empty())
+ {
+ ReadCsv();
+ }
+ }
+
+ /**
+ * @brief Constructor
+ * @param pStream specifies a binary 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.
+ * @param pLineReaderParams specifies how special line formats should be treated.
+ */
+ explicit Document(std::istream& pStream,
+ const LabelParams& pLabelParams = LabelParams(),
+ const SeparatorParams& pSeparatorParams = SeparatorParams(),
+ const ConverterParams& pConverterParams = ConverterParams(),
+ const LineReaderParams& pLineReaderParams = LineReaderParams())
+ : mPath()
+ , mLabelParams(pLabelParams)
+ , mSeparatorParams(pSeparatorParams)
+ , mConverterParams(pConverterParams)
+ , mLineReaderParams(pLineReaderParams)
+ , mData()
+ , mColumnNames()
+ , mRowNames()
+ {
+ ReadCsv(pStream);
+ }
+
+ /**
+ * @brief Read Document data from file.
+ * @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.
+ * @param pLineReaderParams specifies how special line formats should be treated.
+ */
+ void Load(const std::string& pPath,
+ const LabelParams& pLabelParams = LabelParams(),
+ const SeparatorParams& pSeparatorParams = SeparatorParams(),
+ const ConverterParams& pConverterParams = ConverterParams(),
+ const LineReaderParams& pLineReaderParams = LineReaderParams())
+ {
+ mPath = pPath;
+ mLabelParams = pLabelParams;
+ mSeparatorParams = pSeparatorParams;
+ mConverterParams = pConverterParams;
+ mLineReaderParams = pLineReaderParams;
+ ReadCsv();
+ }
+
+ /**
+ * @brief Read Document data from stream.
+ * @param pStream specifies a binary 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.
+ * @param pLineReaderParams specifies how special line formats should be treated.
+ */
+ void Load(std::istream& pStream,
+ const LabelParams& pLabelParams = LabelParams(),
+ const SeparatorParams& pSeparatorParams = SeparatorParams(),
+ const ConverterParams& pConverterParams = ConverterParams(),
+ const LineReaderParams& pLineReaderParams = LineReaderParams())
+ {
+ mPath = "";
+ mLabelParams = pLabelParams;
+ mSeparatorParams = pSeparatorParams;
+ mConverterParams = pConverterParams;
+ mLineReaderParams = pLineReaderParams;
+ ReadCsv(pStream);
+ }
+
+ /**
+ * @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 a binary output stream to write the data to.
+ */
+ void Save(std::ostream& pStream) const
+ {
+ WriteCsv(pStream);
+ }
+
+ /**
+ * @brief Clears loaded Document data.
+ *
+ */
+ void Clear()
+ {
+ mData.clear();
+ mColumnNames.clear();
+ mRowNames.clear();
+#ifdef HAS_CODECVT
+ mIsUtf16 = false;
+ mIsLE = false;
+#endif
+ mHasUtf8BOM = false;
+ }
+
+ /**
+ * @brief Get column index by name.
+ * @param pColumnName column label name.
+ * @returns zero-based column index.
+ */
+ int GetColumnIdx(const std::string& pColumnName) const
+ {
+ if (mLabelParams.mColumnNameIdx >= 0)
+ {
+ if (mColumnNames.find(pColumnName) != mColumnNames.end())
+ {
+ return static_cast<int>(mColumnNames.at(pColumnName)) - (mLabelParams.mRowNameIdx + 1);
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * @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 size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+ 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)
+ {
+ if (dataColumnIdx < itRow->size())
+ {
+ T val;
+ converter.ToVal(itRow->at(dataColumnIdx), val);
+ column.push_back(val);
+ }
+ else
+ {
+ const std::string errStr = "requested column index " +
+ std::to_string(pColumnIdx) + " >= " +
+ std::to_string(itRow->size() - GetDataColumnIndex(0)) +
+ " (number of columns on row index " +
+ std::to_string(std::distance(mData.begin(), itRow) -
+ (mLabelParams.mColumnNameIdx + 1)) + ")";
+ throw std::out_of_range(errStr);
+ }
+ }
+ }
+ return column;
+ }
+
+ /**
+ * @brief Get column by index.
+ * @param pColumnIdx zero-based column index.
+ * @param pToVal conversion function.
+ * @returns vector of column data.
+ */
+ template<typename T>
+ std::vector<T> GetColumn(const size_t pColumnIdx, ConvFunc<T> pToVal) const
+ {
+ const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+ std::vector<T> column;
+ for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
+ {
+ if (std::distance(mData.begin(), itRow) > mLabelParams.mColumnNameIdx)
+ {
+ T val;
+ pToVal(itRow->at(dataColumnIdx), 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 int columnIdx = GetColumnIdx(pColumnName);
+ if (columnIdx < 0)
+ {
+ throw std::out_of_range("column not found: " + pColumnName);
+ }
+ return GetColumn<T>(static_cast<size_t>(columnIdx));
+ }
+
+ /**
+ * @brief Get column by name.
+ * @param pColumnName column label name.
+ * @param pToVal conversion function.
+ * @returns vector of column data.
+ */
+ template<typename T>
+ std::vector<T> GetColumn(const std::string& pColumnName, ConvFunc<T> pToVal) const
+ {
+ const int columnIdx = GetColumnIdx(pColumnName);
+ if (columnIdx < 0)
+ {
+ throw std::out_of_range("column not found: " + pColumnName);
+ }
+ return GetColumn<T>(static_cast<size_t>(columnIdx), pToVal);
+ }
+
+ /**
+ * @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 dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+
+ while (GetDataRowIndex(pColumn.size()) > GetDataRowCount())
+ {
+ std::vector<std::string> row;
+ row.resize(GetDataColumnCount());
+ mData.push_back(row);
+ }
+
+ if ((dataColumnIdx + 1) > GetDataColumnCount())
+ {
+ for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
+ {
+ if (std::distance(mData.begin(), itRow) >= mLabelParams.mColumnNameIdx)
+ {
+ itRow->resize(GetDataColumnIndex(dataColumnIdx + 1));
+ }
+ }
+ }
+
+ Converter<T> converter(mConverterParams);
+ for (auto itRow = pColumn.begin(); itRow != pColumn.end(); ++itRow)
+ {
+ std::string str;
+ converter.ToStr(*itRow, str);
+ mData.at(static_cast<size_t>(std::distance(pColumn.begin(), itRow) + mLabelParams.mColumnNameIdx + 1)).at(
+ dataColumnIdx) = 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 int columnIdx = GetColumnIdx(pColumnName);
+ if (columnIdx < 0)
+ {
+ throw std::out_of_range("column not found: " + pColumnName);
+ }
+ SetColumn<T>(static_cast<size_t>(columnIdx), pColumn);
+ }
+
+ /**
+ * @brief Remove column by index.
+ * @param pColumnIdx zero-based column index.
+ */
+ void RemoveColumn(const size_t pColumnIdx)
+ {
+ const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+ for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
+ {
+ if (std::distance(mData.begin(), itRow) >= mLabelParams.mColumnNameIdx)
+ {
+ itRow->erase(itRow->begin() + static_cast<int>(dataColumnIdx));
+ }
+ }
+
+ UpdateColumnNames();
+ }
+
+ /**
+ * @brief Remove column by name.
+ * @param pColumnName column label name.
+ */
+ void RemoveColumn(const std::string& pColumnName)
+ {
+ int columnIdx = GetColumnIdx(pColumnName);
+ if (columnIdx < 0)
+ {
+ throw std::out_of_range("column not found: " + pColumnName);
+ }
+
+ RemoveColumn(static_cast<size_t>(columnIdx));
+ }
+
+ /**
+ * @brief Insert column at specified index.
+ * @param pColumnIdx zero-based column index.
+ * @param pColumn vector of column data (optional argument).
+ * @param pColumnName column label name (optional argument).
+ */
+ template<typename T>
+ void InsertColumn(const size_t pColumnIdx, const std::vector<T>& pColumn = std::vector<T>(),
+ const std::string& pColumnName = std::string())
+ {
+ const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+
+ std::vector<std::string> column;
+ if (pColumn.empty())
+ {
+ column.resize(GetDataRowCount());
+ }
+ else
+ {
+ column.resize(GetDataRowIndex(pColumn.size()));
+ Converter<T> converter(mConverterParams);
+ for (auto itRow = pColumn.begin(); itRow != pColumn.end(); ++itRow)
+ {
+ std::string str;
+ converter.ToStr(*itRow, str);
+ const size_t rowIdx =
+ static_cast<size_t>(std::distance(pColumn.begin(), itRow) + (mLabelParams.mColumnNameIdx + 1));
+ column.at(rowIdx) = str;
+ }
+ }
+
+ while (column.size() > GetDataRowCount())
+ {
+ std::vector<std::string> row;
+ const size_t columnCount = std::max<size_t>(static_cast<size_t>(mLabelParams.mColumnNameIdx + 1),
+ GetDataColumnCount());
+ row.resize(columnCount);
+ mData.push_back(row);
+ }
+
+ for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
+ {
+ if (std::distance(mData.begin(), itRow) >= mLabelParams.mColumnNameIdx)
+ {
+ const size_t rowIdx = static_cast<size_t>(std::distance(mData.begin(), itRow));
+ itRow->insert(itRow->begin() + static_cast<int>(dataColumnIdx), column.at(rowIdx));
+ }
+ }
+
+ if (!pColumnName.empty())
+ {
+ SetColumnName(pColumnIdx, pColumnName);
+ }
+
+ UpdateColumnNames();
+ }
+
+ /**
+ * @brief Get number of data columns (excluding label columns).
+ * @returns column count.
+ */
+ size_t GetColumnCount() const
+ {
+ const int count = static_cast<int>((mData.size() > 0) ? mData.at(0).size() : 0) -
+ (mLabelParams.mRowNameIdx + 1);
+ return (count >= 0) ? static_cast<size_t>(count) : 0;
+ }
+
+ /**
+ * @brief Get row index by name.
+ * @param pRowName row label name.
+ * @returns zero-based row index.
+ */
+ int GetRowIdx(const std::string& pRowName) const
+ {
+ if (mLabelParams.mRowNameIdx >= 0)
+ {
+ if (mRowNames.find(pRowName) != mRowNames.end())
+ {
+ return static_cast<int>(mRowNames.at(pRowName)) - (mLabelParams.mColumnNameIdx + 1);
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * @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 size_t dataRowIdx = GetDataRowIndex(pRowIdx);
+ std::vector<T> row;
+ Converter<T> converter(mConverterParams);
+ for (auto itCol = mData.at(dataRowIdx).begin(); itCol != mData.at(dataRowIdx).end(); ++itCol)
+ {
+ if (std::distance(mData.at(dataRowIdx).begin(), itCol) > mLabelParams.mRowNameIdx)
+ {
+ T val;
+ converter.ToVal(*itCol, val);
+ row.push_back(val);
+ }
+ }
+ return row;
+ }
+
+ /**
+ * @brief Get row by index.
+ * @param pRowIdx zero-based row index.
+ * @param pToVal conversion function.
+ * @returns vector of row data.
+ */
+ template<typename T>
+ std::vector<T> GetRow(const size_t pRowIdx, ConvFunc<T> pToVal) const
+ {
+ const size_t dataRowIdx = GetDataRowIndex(pRowIdx);
+ std::vector<T> row;
+ Converter<T> converter(mConverterParams);
+ for (auto itCol = mData.at(dataRowIdx).begin(); itCol != mData.at(dataRowIdx).end(); ++itCol)
+ {
+ if (std::distance(mData.at(dataRowIdx).begin(), itCol) > mLabelParams.mRowNameIdx)
+ {
+ T val;
+ pToVal(*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
+ {
+ int rowIdx = GetRowIdx(pRowName);
+ if (rowIdx < 0)
+ {
+ throw std::out_of_range("row not found: " + pRowName);
+ }
+ return GetRow<T>(static_cast<size_t>(rowIdx));
+ }
+
+ /**
+ * @brief Get row by name.
+ * @param pRowName row label name.
+ * @param pToVal conversion function.
+ * @returns vector of row data.
+ */
+ template<typename T>
+ std::vector<T> GetRow(const std::string& pRowName, ConvFunc<T> pToVal) const
+ {
+ int rowIdx = GetRowIdx(pRowName);
+ if (rowIdx < 0)
+ {
+ throw std::out_of_range("row not found: " + pRowName);
+ }
+ return GetRow<T>(static_cast<size_t>(rowIdx), pToVal);
+ }
+
+ /**
+ * @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 dataRowIdx = GetDataRowIndex(pRowIdx);
+
+ while ((dataRowIdx + 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)
+ {
+ if (std::distance(mData.begin(), itRow) >= mLabelParams.mColumnNameIdx)
+ {
+ itRow->resize(GetDataColumnIndex(pRow.size()));
+ }
+ }
+ }
+
+ Converter<T> converter(mConverterParams);
+ for (auto itCol = pRow.begin(); itCol != pRow.end(); ++itCol)
+ {
+ std::string str;
+ converter.ToStr(*itCol, str);
+ mData.at(dataRowIdx).at(static_cast<size_t>(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)
+ {
+ int rowIdx = GetRowIdx(pRowName);
+ if (rowIdx < 0)
+ {
+ throw std::out_of_range("row not found: " + pRowName);
+ }
+ return SetRow<T>(static_cast<size_t>(rowIdx), pRow);
+ }
+
+ /**
+ * @brief Remove row by index.
+ * @param pRowIdx zero-based row index.
+ */
+ void RemoveRow(const size_t pRowIdx)
+ {
+ const size_t dataRowIdx = GetDataRowIndex(pRowIdx);
+ mData.erase(mData.begin() + static_cast<int>(dataRowIdx));
+ UpdateRowNames();
+ }
+
+ /**
+ * @brief Remove row by name.
+ * @param pRowName row label name.
+ */
+ void RemoveRow(const std::string& pRowName)
+ {
+ int rowIdx = GetRowIdx(pRowName);
+ if (rowIdx < 0)
+ {
+ throw std::out_of_range("row not found: " + pRowName);
+ }
+
+ RemoveRow(static_cast<size_t>(rowIdx));
+ }
+
+ /**
+ * @brief Insert row at specified index.
+ * @param pRowIdx zero-based row index.
+ * @param pRow vector of row data (optional argument).
+ * @param pRowName row label name (optional argument).
+ */
+ template<typename T>
+ void InsertRow(const size_t pRowIdx, const std::vector<T>& pRow = std::vector<T>(),
+ const std::string& pRowName = std::string())
+ {
+ const size_t rowIdx = GetDataRowIndex(pRowIdx);
+
+ std::vector<std::string> row;
+ if (pRow.empty())
+ {
+ row.resize(GetDataColumnCount());
+ }
+ else
+ {
+ row.resize(GetDataColumnIndex(pRow.size()));
+ Converter<T> converter(mConverterParams);
+ for (auto itCol = pRow.begin(); itCol != pRow.end(); ++itCol)
+ {
+ std::string str;
+ converter.ToStr(*itCol, str);
+ row.at(static_cast<size_t>(std::distance(pRow.begin(), itCol) + mLabelParams.mRowNameIdx + 1)) = str;
+ }
+ }
+
+ while (rowIdx > GetDataRowCount())
+ {
+ std::vector<std::string> tempRow;
+ tempRow.resize(GetDataColumnCount());
+ mData.push_back(tempRow);
+ }
+
+ mData.insert(mData.begin() + static_cast<int>(rowIdx), row);
+
+ if (!pRowName.empty())
+ {
+ SetRowName(pRowIdx, pRowName);
+ }
+
+ UpdateRowNames();
+ }
+
+ /**
+ * @brief Get number of data rows (excluding label rows).
+ * @returns row count.
+ */
+ size_t GetRowCount() const
+ {
+ const int count = static_cast<int>(mData.size()) - (mLabelParams.mColumnNameIdx + 1);
+ return (count >= 0) ? static_cast<size_t>(count) : 0;
+ }
+
+ /**
+ * @brief Get cell by index.
+ * @param pColumnIdx zero-based column index.
+ * @param pRowIdx zero-based row index.
+ * @returns cell data.
+ */
+ template<typename T>
+ T GetCell(const size_t pColumnIdx, const size_t pRowIdx) const
+ {
+ const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+ const size_t dataRowIdx = GetDataRowIndex(pRowIdx);
+
+ T val;
+ Converter<T> converter(mConverterParams);
+ converter.ToVal(mData.at(dataRowIdx).at(dataColumnIdx), val);
+ return val;
+ }
+
+ /**
+ * @brief Get cell by index.
+ * @param pColumnIdx zero-based column index.
+ * @param pRowIdx zero-based row index.
+ * @param pToVal conversion function.
+ * @returns cell data.
+ */
+ template<typename T>
+ T GetCell(const size_t pColumnIdx, const size_t pRowIdx, ConvFunc<T> pToVal) const
+ {
+ const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+ const size_t dataRowIdx = GetDataRowIndex(pRowIdx);
+
+ T val;
+ pToVal(mData.at(dataRowIdx).at(dataColumnIdx), 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 int columnIdx = GetColumnIdx(pColumnName);
+ if (columnIdx < 0)
+ {
+ throw std::out_of_range("column not found: " + pColumnName);
+ }
+
+ const int rowIdx = GetRowIdx(pRowName);
+ if (rowIdx < 0)
+ {
+ throw std::out_of_range("row not found: " + pRowName);
+ }
+
+ return GetCell<T>(static_cast<size_t>(columnIdx), static_cast<size_t>(rowIdx));
+ }
+
+ /**
+ * @brief Get cell by name.
+ * @param pColumnName column label name.
+ * @param pRowName row label name.
+ * @param pToVal conversion function.
+ * @returns cell data.
+ */
+ template<typename T>
+ T GetCell(const std::string& pColumnName, const std::string& pRowName, ConvFunc<T> pToVal) const
+ {
+ const int columnIdx = GetColumnIdx(pColumnName);
+ if (columnIdx < 0)
+ {
+ throw std::out_of_range("column not found: " + pColumnName);
+ }
+
+ const int rowIdx = GetRowIdx(pRowName);
+ if (rowIdx < 0)
+ {
+ throw std::out_of_range("row not found: " + pRowName);
+ }
+
+ return GetCell<T>(static_cast<size_t>(columnIdx), static_cast<size_t>(rowIdx), pToVal);
+ }
+
+ /**
+ * @brief Get cell by column name and row index.
+ * @param pColumnName column label name.
+ * @param pRowIdx zero-based row index.
+ * @returns cell data.
+ */
+ template<typename T>
+ T GetCell(const std::string& pColumnName, const size_t pRowIdx) const
+ {
+ const int columnIdx = GetColumnIdx(pColumnName);
+ if (columnIdx < 0)
+ {
+ throw std::out_of_range("column not found: " + pColumnName);
+ }
+
+ return GetCell<T>(static_cast<size_t>(columnIdx), pRowIdx);
+ }
+
+ /**
+ * @brief Get cell by column name and row index.
+ * @param pColumnName column label name.
+ * @param pRowIdx zero-based row index.
+ * @param pToVal conversion function.
+ * @returns cell data.
+ */
+ template<typename T>
+ T GetCell(const std::string& pColumnName, const size_t pRowIdx, ConvFunc<T> pToVal) const
+ {
+ const int columnIdx = GetColumnIdx(pColumnName);
+ if (columnIdx < 0)
+ {
+ throw std::out_of_range("column not found: " + pColumnName);
+ }
+
+ return GetCell<T>(static_cast<size_t>(columnIdx), pRowIdx, pToVal);
+ }
+
+ /**
+ * @brief Get cell by column index and row name.
+ * @param pColumnIdx zero-based column index.
+ * @param pRowName row label name.
+ * @returns cell data.
+ */
+ template<typename T>
+ T GetCell(const size_t pColumnIdx, const std::string& pRowName) const
+ {
+ const int rowIdx = GetRowIdx(pRowName);
+ if (rowIdx < 0)
+ {
+ throw std::out_of_range("row not found: " + pRowName);
+ }
+
+ return GetCell<T>(pColumnIdx, static_cast<size_t>(rowIdx));
+ }
+
+ /**
+ * @brief Get cell by column index and row name.
+ * @param pColumnIdx zero-based column index.
+ * @param pRowName row label name.
+ * @param pToVal conversion function.
+ * @returns cell data.
+ */
+ template<typename T>
+ T GetCell(const size_t pColumnIdx, const std::string& pRowName, ConvFunc<T> pToVal) const
+ {
+ const int rowIdx = GetRowIdx(pRowName);
+ if (rowIdx < 0)
+ {
+ throw std::out_of_range("row not found: " + pRowName);
+ }
+
+ return GetCell<T>(pColumnIdx, static_cast<size_t>(rowIdx), pToVal);
+ }
+
+ /**
+ * @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 dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+ const size_t dataRowIdx = GetDataRowIndex(pRowIdx);
+
+ while ((dataRowIdx + 1) > GetDataRowCount())
+ {
+ std::vector<std::string> row;
+ row.resize(GetDataColumnCount());
+ mData.push_back(row);
+ }
+
+ if ((dataColumnIdx + 1) > GetDataColumnCount())
+ {
+ for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow)
+ {
+ if (std::distance(mData.begin(), itRow) >= mLabelParams.mColumnNameIdx)
+ {
+ itRow->resize(dataColumnIdx + 1);
+ }
+ }
+ }
+
+ std::string str;
+ Converter<T> converter(mConverterParams);
+ converter.ToStr(pCell, str);
+ mData.at(dataRowIdx).at(dataColumnIdx) = 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 int columnIdx = GetColumnIdx(pColumnName);
+ if (columnIdx < 0)
+ {
+ throw std::out_of_range("column not found: " + pColumnName);
+ }
+
+ const int rowIdx = GetRowIdx(pRowName);
+ if (rowIdx < 0)
+ {
+ throw std::out_of_range("row not found: " + pRowName);
+ }
+
+ SetCell<T>(static_cast<size_t>(columnIdx), static_cast<size_t>(rowIdx), pCell);
+ }
+
+ /**
+ * @brief Set cell by column index and row name.
+ * @param pColumnIdx zero-based column index.
+ * @param pRowName row label name.
+ * @param pCell cell data.
+ */
+ template<typename T>
+ void SetCell(const size_t pColumnIdx, const std::string& pRowName, const T& pCell)
+ {
+ const int rowIdx = GetRowIdx(pRowName);
+ if (rowIdx < 0)
+ {
+ throw std::out_of_range("row not found: " + pRowName);
+ }
+
+ SetCell<T>(pColumnIdx, static_cast<size_t>(rowIdx), pCell);
+ }
+
+ /**
+ * @brief Set cell by column name and row index.
+ * @param pColumnName column label name.
+ * @param pRowIdx zero-based row index.
+ * @param pCell cell data.
+ */
+ template<typename T>
+ void SetCell(const std::string& pColumnName, const size_t pRowIdx, const T& pCell)
+ {
+ const int columnIdx = GetColumnIdx(pColumnName);
+ if (columnIdx < 0)
+ {
+ throw std::out_of_range("column not found: " + pColumnName);
+ }
+
+ SetCell<T>(static_cast<size_t>(columnIdx), pRowIdx, pCell);
+ }
+
+ /**
+ * @brief Get column name
+ * @param pColumnIdx zero-based column index.
+ * @returns column name.
+ */
+ std::string GetColumnName(const size_t pColumnIdx) const
+ {
+ const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+ if (mLabelParams.mColumnNameIdx < 0)
+ {
+ throw std::out_of_range("column name row index < 0: " + std::to_string(mLabelParams.mColumnNameIdx));
+ }
+
+ return mData.at(static_cast<size_t>(mLabelParams.mColumnNameIdx)).at(dataColumnIdx);
+ }
+
+ /**
+ * @brief Set column name
+ * @param pColumnIdx zero-based column index.
+ * @param pColumnName column name.
+ */
+ void SetColumnName(size_t pColumnIdx, const std::string& pColumnName)
+ {
+ if (mLabelParams.mColumnNameIdx < 0)
+ {
+ throw std::out_of_range("column name row index < 0: " + std::to_string(mLabelParams.mColumnNameIdx));
+ }
+
+ const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx);
+ mColumnNames[pColumnName] = dataColumnIdx;
+
+ // increase table size if necessary:
+ const size_t rowIdx = static_cast<size_t>(mLabelParams.mColumnNameIdx);
+ if (rowIdx >= mData.size())
+ {
+ mData.resize(rowIdx + 1);
+ }
+ auto& row = mData[rowIdx];
+ if (dataColumnIdx >= row.size())
+ {
+ row.resize(dataColumnIdx + 1);
+ }
+
+ mData.at(static_cast<size_t>(mLabelParams.mColumnNameIdx)).at(dataColumnIdx) = pColumnName;
+ }
+
+ /**
+ * @brief Get column names
+ * @returns vector of column names.
+ */
+ std::vector<std::string> GetColumnNames() const
+ {
+ if (mLabelParams.mColumnNameIdx >= 0)
+ {
+ return std::vector<std::string>(mData.at(static_cast<size_t>(mLabelParams.mColumnNameIdx)).begin() +
+ (mLabelParams.mRowNameIdx + 1),
+ mData.at(static_cast<size_t>(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 size_t pRowIdx) const
+ {
+ const size_t dataRowIdx = GetDataRowIndex(pRowIdx);
+ if (mLabelParams.mRowNameIdx < 0)
+ {
+ throw std::out_of_range("row name column index < 0: " + std::to_string(mLabelParams.mRowNameIdx));
+ }
+
+ return mData.at(dataRowIdx).at(static_cast<size_t>(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 size_t dataRowIdx = GetDataRowIndex(pRowIdx);
+ mRowNames[pRowName] = dataRowIdx;
+ if (mLabelParams.mRowNameIdx < 0)
+ {
+ throw std::out_of_range("row name column index < 0: " + std::to_string(mLabelParams.mRowNameIdx));
+ }
+
+ // increase table size if necessary:
+ if (dataRowIdx >= mData.size())
+ {
+ mData.resize(dataRowIdx + 1);
+ }
+ auto& row = mData[dataRowIdx];
+ if (mLabelParams.mRowNameIdx >= static_cast<int>(row.size()))
+ {
+ row.resize(static_cast<size_t>(mLabelParams.mRowNameIdx) + 1);
+ }
+
+ mData.at(dataRowIdx).at(static_cast<size_t>(mLabelParams.mRowNameIdx)) = pRowName;
+ }
+
+ /**
+ * @brief Get row names
+ * @returns vector of row names.
+ */
+ std::vector<std::string> GetRowNames() const
+ {
+ 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(static_cast<size_t>(mLabelParams.mRowNameIdx)));
+ }
+ }
+ }
+ return rownames;
+ }
+
+ private:
+ void ReadCsv()
+ {
+ std::ifstream stream;
+ stream.exceptions(std::ifstream::failbit | std::ifstream::badbit);
+ stream.open(mPath, std::ios::binary);
+ ReadCsv(stream);
+ }
+
+ void ReadCsv(std::istream& pStream)
+ {
+ Clear();
+ pStream.seekg(0, std::ios::end);
+ std::streamsize length = pStream.tellg();
+ pStream.seekg(0, std::ios::beg);
+
+#ifdef HAS_CODECVT
+ std::vector<char> bom2b(2, '\0');
+ if (length >= 2)
+ {
+ pStream.read(bom2b.data(), 2);
+ pStream.seekg(0, std::ios::beg);
+ }
+
+ static const std::vector<char> bomU16le = { '\xff', '\xfe' };
+ static const std::vector<char> bomU16be = { '\xfe', '\xff' };
+ if ((bom2b == bomU16le) || (bom2b == bomU16be))
+ {
+ mIsUtf16 = true;
+ mIsLE = (bom2b == 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);
+ ParseCsv(ss, static_cast<std::streamsize>(utf8.size()));
+ }
+ else
+#endif
+ {
+ // check for UTF-8 Byte order mark and skip it when found
+ if (length >= 3)
+ {
+ std::vector<char> bom3b(3, '\0');
+ pStream.read(bom3b.data(), 3);
+
+ if (bom3b != s_Utf8BOM)
+ {
+ // file does not start with a UTF-8 Byte order mark
+ pStream.seekg(0, std::ios::beg);
+ }
+ else
+ {
+ // file did start with a UTF-8 Byte order mark, simply skip it
+ length -= 3;
+ mHasUtf8BOM = true;
+ }
+ }
+
+ ParseCsv(pStream, length);
+ }
+ }
+
+ void ParseCsv(std::istream& pStream, std::streamsize p_FileLength)
+ {
+ 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 (p_FileLength > 0)
+ {
+ const std::streamsize toReadLength = std::min<std::streamsize>(p_FileLength, bufLength);
+ pStream.read(buffer.data(), toReadLength);
+
+ // With user-specified istream opened in non-binary mode on windows, we may have a
+ // data length mismatch, so ensure we don't parse outside actual data length read.
+ const std::streamsize readLength = pStream.gcount();
+ if (readLength <= 0)
+ {
+ break;
+ }
+
+ for (size_t i = 0; i < static_cast<size_t>(readLength); ++i)
+ {
+ if (buffer[i] == mSeparatorParams.mQuoteChar)
+ {
+ if (cell.empty() || (cell[0] == mSeparatorParams.mQuoteChar))
+ {
+ quoted = !quoted;
+ }
+ else if (mSeparatorParams.mTrim)
+ {
+ // allow whitespace before first mQuoteChar
+ const auto firstQuote = std::find(cell.begin(), cell.end(), mSeparatorParams.mQuoteChar);
+ if (std::all_of(cell.begin(), firstQuote, [](int ch) { return isspace(ch); }))
+ {
+ quoted = !quoted;
+ }
+ }
+ cell += buffer[i];
+ }
+ else if (buffer[i] == mSeparatorParams.mSeparator)
+ {
+ if (!quoted)
+ {
+ row.push_back(Unquote(Trim(cell)));
+ cell.clear();
+ }
+ else
+ {
+ cell += buffer[i];
+ }
+ }
+ else if (buffer[i] == '\r')
+ {
+ if (mSeparatorParams.mQuotedLinebreaks && quoted)
+ {
+ cell += buffer[i];
+ }
+ else
+ {
+ ++cr;
+ }
+ }
+ else if (buffer[i] == '\n')
+ {
+ if (mSeparatorParams.mQuotedLinebreaks && quoted)
+ {
+ cell += buffer[i];
+ }
+ else
+ {
+ ++lf;
+ if (mLineReaderParams.mSkipEmptyLines && row.empty() && cell.empty())
+ {
+ // skip empty line
+ }
+ else
+ {
+ row.push_back(Unquote(Trim(cell)));
+
+ if (mLineReaderParams.mSkipCommentLines && !row.at(0).empty() &&
+ (row.at(0)[0] == mLineReaderParams.mCommentPrefix))
+ {
+ // skip comment line
+ }
+ else
+ {
+ mData.push_back(row);
+ }
+
+ cell.clear();
+ row.clear();
+ quoted = false;
+ }
+ }
+ }
+ else
+ {
+ cell += buffer[i];
+ }
+ }
+ p_FileLength -= readLength;
+ }
+
+ // Handle last cell without linebreak
+ if (!cell.empty())
+ {
+ row.push_back(Unquote(Trim(cell)));
+ cell.clear();
+ }
+
+ // Handle last line without linebreak
+ if (!row.empty())
+ {
+ if (mLineReaderParams.mSkipCommentLines && !row.at(0).empty() &&
+ (row.at(0)[0] == mLineReaderParams.mCommentPrefix))
+ {
+ // skip comment line
+ }
+ else
+ {
+ 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
+ UpdateColumnNames();
+
+ // Set up row labels
+ UpdateRowNames();
+ }
+
+ 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 << static_cast<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);
+ if (mHasUtf8BOM)
+ {
+ stream.write(s_Utf8BOM.data(), 3);
+ }
+
+ 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 (mSeparatorParams.mAutoQuote &&
+ ((itc->find(mSeparatorParams.mSeparator) != std::string::npos) ||
+ (itc->find(' ') != std::string::npos) ||
+ (itc->find('\n') != std::string::npos)))
+ {
+ // escape quotes in string
+ std::string str = *itc;
+ const std::string quoteCharStr = std::string(1, mSeparatorParams.mQuoteChar);
+ ReplaceString(str, quoteCharStr, quoteCharStr + quoteCharStr);
+
+ pStream << quoteCharStr << str << quoteCharStr;
+ }
+ else
+ {
+ pStream << *itc;
+ }
+
+ if (std::distance(itc, itr->end()) > 1)
+ {
+ pStream << mSeparatorParams.mSeparator;
+ }
+ }
+ pStream << (mSeparatorParams.mHasCR ? "\r\n" : "\n");
+ }
+ }
+
+ size_t GetDataRowCount() const
+ {
+ return mData.size();
+ }
+
+ size_t GetDataColumnCount() const
+ {
+ const size_t firstDataRow = static_cast<size_t>((mLabelParams.mColumnNameIdx >= 0) ? mLabelParams.mColumnNameIdx : 0);
+ return (mData.size() > firstDataRow) ? mData.at(firstDataRow).size() : 0;
+ }
+
+ inline size_t GetDataRowIndex(const size_t pRowIdx) const
+ {
+ const size_t firstDataRow = static_cast<size_t>((mLabelParams.mColumnNameIdx + 1 >= 0) ? mLabelParams.mColumnNameIdx + 1 : 0);
+ return pRowIdx + firstDataRow;
+ }
+
+ inline size_t GetDataColumnIndex(const size_t pColumnIdx) const
+ {
+ const size_t firstDataColumn = static_cast<size_t>((mLabelParams.mRowNameIdx + 1 >= 0) ? mLabelParams.mRowNameIdx + 1 : 0);
+ return pColumnIdx + firstDataColumn;
+ }
+
+ std::string Trim(const std::string& pStr) const
+ {
+ if (mSeparatorParams.mTrim)
+ {
+ std::string str = pStr;
+
+ // ltrim
+ str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](int ch) { return !isspace(ch); }));
+
+ // rtrim
+ str.erase(std::find_if(str.rbegin(), str.rend(), [](int ch) { return !isspace(ch); }).base(), str.end());
+
+ return str;
+ }
+ else
+ {
+ return pStr;
+ }
+ }
+
+ std::string Unquote(const std::string& pStr) const
+ {
+ if (mSeparatorParams.mAutoQuote && (pStr.size() >= 2) &&
+ (pStr.front() == mSeparatorParams.mQuoteChar) &&
+ (pStr.back() == mSeparatorParams.mQuoteChar))
+ {
+ // remove start/end quotes
+ std::string str = pStr.substr(1, pStr.size() - 2);
+
+ // unescape quotes in string
+ const std::string quoteCharStr = std::string(1, mSeparatorParams.mQuoteChar);
+ ReplaceString(str, quoteCharStr + quoteCharStr, quoteCharStr);
+
+ return str;
+ }
+ else
+ {
+ return pStr;
+ }
+ }
+
+ void UpdateColumnNames()
+ {
+ mColumnNames.clear();
+ if ((mLabelParams.mColumnNameIdx >= 0) &&
+ (static_cast<int>(mData.size()) > mLabelParams.mColumnNameIdx))
+ {
+ size_t i = 0;
+ for (auto& columnName : mData[static_cast<size_t>(mLabelParams.mColumnNameIdx)])
+ {
+ mColumnNames[columnName] = i++;
+ }
+ }
+ }
+
+ void UpdateRowNames()
+ {
+ mRowNames.clear();
+ if ((mLabelParams.mRowNameIdx >= 0) &&
+ (static_cast<int>(mData.size()) >
+ (mLabelParams.mColumnNameIdx + 1)))
+ {
+ size_t i = 0;
+ for (auto& dataRow : mData)
+ {
+ if (static_cast<int>(dataRow.size()) > mLabelParams.mRowNameIdx)
+ {
+ mRowNames[dataRow[static_cast<size_t>(mLabelParams.mRowNameIdx)]] = i++;
+ }
+ }
+ }
+ }
+
+#ifdef HAS_CODECVT
+#if defined(_MSC_VER)
+#pragma warning (push)
+#pragma warning (disable: 4996)
+#endif
+ static std::string ToString(const std::wstring& pWStr)
+ {
+ return std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>{ }.to_bytes(pWStr);
+ }
+
+ static std::wstring ToWString(const std::string& pStr)
+ {
+ return std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>{ }.from_bytes(pStr);
+ }
+#if defined(_MSC_VER)
+#pragma warning (pop)
+#endif
+#endif
+
+ static void ReplaceString(std::string& pStr, const std::string& pSearch, const std::string& pReplace)
+ {
+ size_t pos = 0;
+
+ while ((pos = pStr.find(pSearch, pos)) != std::string::npos)
+ {
+ pStr.replace(pos, pSearch.size(), pReplace);
+ pos += pReplace.size();
+ }
+ }
+
+ private:
+ std::string mPath;
+ LabelParams mLabelParams;
+ SeparatorParams mSeparatorParams;
+ ConverterParams mConverterParams;
+ LineReaderParams mLineReaderParams;
+ 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
+ bool mHasUtf8BOM = false;
+ };
+}