#include "TableTemplate.hpp" #include #include #include #include bool TableCell::IsDataHoldingCell() const { return IsPrimaryCell() || !IsMergedCell(); } bool TableCell::IsPrimaryCell() const { return PrimaryCellLocation == Location; } bool TableCell::IsMergedCell() const { return PrimaryCellLocation.x == -1 || PrimaryCellLocation.y == -1; } Vec2i TableArrayGroup::GetLeftCell() const { return { Row, LeftCell }; } Vec2i TableArrayGroup::GetRightCell() const { return { Row, RightCell }; } int TableArrayGroup::GetCount() const { return RightCell - LeftCell + 1; } Vec2i TableArrayGroup::FindCell(std::string_view name) { } TableInstantiationParameters::TableInstantiationParameters(const TableTemplate& table) : mTable{ &table } { } TableInstantiationParameters& TableInstantiationParameters::ResetTable(const TableTemplate& newTable) { mTable = &newTable; return *this; } TableInstantiationParameters TableInstantiationParameters::RebindTable(const TableTemplate& newTable) const { TableInstantiationParameters result(newTable); result.SingularCells = this->SingularCells; result.ArrayGroups = this->ArrayGroups; return result; } const TableTemplate& TableInstantiationParameters::GetTable() const { return *mTable; } bool TableTemplate::IsInstance(const Template* tmpl) { return tmpl->GetKind() == KD_Table; } TableTemplate::TableTemplate() : Template(KD_Table) { } int TableTemplate::GetTableWidth() const { return mColumnWidths.size(); } int TableTemplate::GetTableHeight() const { return mRowHeights.size(); } void TableTemplate::Resize(int newWidth, int newHeight) { // TODO this doesn't gracefully handle resizing to a smaller size which trims some merged cells std::vector cells; cells.reserve(newWidth * newHeight); int tableWidth = GetTableWidth(); int tableHeight = GetTableHeight(); for (int y = 0; y < newHeight; ++y) { if (y >= tableHeight) { for (int x = 0; x < newWidth; ++x) { cells.push_back(TableCell{}); } continue; } for (int x = 0; x < newWidth; ++x) { if (x >= tableWidth) { cells.push_back(TableCell{}); } else { auto& cell = GetCell({ x, y }); cells.push_back(std::move(cell)); } } } mCells = std::move(cells); mColumnWidths.resize(newWidth, 80); mRowHeights.resize(newHeight, 20); } int TableTemplate::GetRowHeight(int row) const { return mRowHeights[row]; } void TableTemplate::SetRowHeight(int row, int height) { mRowHeights[row] = height; } int TableTemplate::GetColumnWidth(int column) const { return mColumnWidths[column]; } void TableTemplate::SetColumnWidth(int column, int width) { mColumnWidths[column] = width; } const TableCell& TableTemplate::GetCell(Vec2i pos) const { int tableWidth = GetTableWidth(); return mCells[pos.y * tableWidth + pos.x]; } TableCell& TableTemplate::GetCell(Vec2i pos) { return const_cast(const_cast(this)->GetCell(pos)); } void TableTemplate::SetCellType(Vec2i pos, TableCell::CellType type) { auto& cell = GetCell(pos); if (cell.Type == type) { return; } switch (cell.Type) { // Nothing to change case TableCell::ConstantCell: break; case TableCell::SingularParametricCell: mName2Parameters.erase(cell.Content); break; case TableCell::ArrayParametricCell: { auto& ag = mArrayGroups[cell.DataId]; if (cell.Location.x == ag.LeftCell) { ag.LeftCell++; } else if (cell.Location.x == ag.RightCell) { ag.RightCell--; } else { } } break; } switch (type) { // Nothing to do case TableCell::ConstantCell: break; case TableCell::SingularParametricCell: { int idx = pos.y * GetTableWidth() + pos.x; auto [DISCARD, inserted] = mName2Parameters.emplace(cell.Content, idx); // Duplicate name if (!inserted) { return; } } break; case TableCell::ArrayParametricCell: { auto ptr = AddArrayGroup(pos.y, pos.x, pos.x); // Duplicate name if (ptr == nullptr) { return; } } break; } cell.Type = type; } int TableTemplate::GetArrayGroupCount() const { return mArrayGroups.size(); } const TableArrayGroup& TableTemplate::GetArrayGroup(int id) const { return mArrayGroups[id]; } TableArrayGroup& TableTemplate::GetArrayGroup(int id) { return mArrayGroups[id]; } TableArrayGroup* TableTemplate::AddArrayGroup(int row, int left, int right) { // size_t max value: 18446744073709551615 // ^~~~~~~~~~~~~~~~~~~~ 20 chars char name[20]; auto res = std::to_chars(std::begin(name), std::end(name), mArrayGroups.size()); std::string_view nameStr(name, res.ptr - name); return AddArrayGroup(nameStr, row, left, right); } TableArrayGroup* TableTemplate::AddArrayGroup(std::string_view name, int row, int left, int right) { assert(row >= 0 && row < GetTableHeight()); assert(left >= 0 && left < GetTableWidth()); assert(right >= 0 && right < GetTableWidth()); // TODO check for overlap if (left > right) { std::swap(left, right); } auto [DISCARD, inserted] = mName2ArrayGroups.emplace(name, (int)mArrayGroups.size()); if (!inserted) { return nullptr; } mArrayGroups.push_back(TableArrayGroup{ .Row = row, .LeftCell = left, .RightCell = right, }); return &mArrayGroups.back(); } bool TableTemplate::ExtendArrayGroupLeft(int id, int n) { assert(n > 0); auto& ag = mArrayGroups[id]; ag.LeftCell -= n; return false; } bool TableTemplate::ExtendArrayGroupRight(int id, int n) { assert(n > 0); auto& ag = mArrayGroups[id]; ag.RightCell += n; return false; } TableCell* TableTemplate::FindCell(std::string_view name) { auto iter = mName2Parameters.find(name); if (iter != mName2Parameters.end()) { return &mCells[iter.value()]; } else { return nullptr; } } TableArrayGroup* TableTemplate::FindArrayGroup(std::string_view name) { auto iter = mName2ArrayGroups.find(name); if (iter != mName2ArrayGroups.end()) { return &mArrayGroups[iter.value()]; } else { return nullptr; } } TableTemplate::MergeCellsResult TableTemplate::MergeCells(Vec2i topLeft, Vec2i bottomRight) { auto SortTwo = [](int& a, int& b) { if (a > b) { std::swap(a, b); } }; SortTwo(topLeft.x, bottomRight.x); SortTwo(topLeft.y, bottomRight.y); auto ResetProgress = [&]() { for (int y = topLeft.y; y < bottomRight.y; ++y) { for (int x = topLeft.x; x < bottomRight.x; ++x) { auto& cell = GetCell({ x, y }); cell.PrimaryCellLocation = { -1, -1 }; } } }; for (int y = topLeft.y; y < bottomRight.y; ++y) { for (int x = topLeft.x; x < bottomRight.x; ++x) { auto& cell = GetCell({ x, y }); if (cell.IsMergedCell()) { ResetProgress(); return MCR_CellAlreadyMerged; } cell.PrimaryCellLocation = topLeft; } } auto& primaryCell = GetCell(topLeft); primaryCell.SpanX = bottomRight.x - topLeft.x; primaryCell.SpanY = bottomRight.y - topLeft.y; return MCR_Success; } TableTemplate::BreakCellsResult TableTemplate::BreakCells(Vec2i topLeft) { auto& primaryCell = GetCell(topLeft); if (!primaryCell.IsMergedCell()) { return BCR_CellNotMerged; } for (int dy = 0; dy < primaryCell.SpanY; ++dy) { for (int dx = 0; dx < primaryCell.SpanX; ++dx) { auto& cell = GetCell({ topLeft.x + dx, topLeft.y + dy }); cell.PrimaryCellLocation = { -1, -1 }; } } primaryCell.SpanX = 1; primaryCell.SpanY = 1; return BCR_Success; } lxw_workbook* TableTemplate::InstantiateToExcelWorkbook(const TableInstantiationParameters& params) const { auto workbook = workbook_new("Table.xlsx"); InstantiateToExcelWorksheet(workbook, params); return workbook; } lxw_worksheet* TableTemplate::InstantiateToExcelWorksheet(lxw_workbook* workbook, const TableInstantiationParameters& params) const { auto worksheet = workbook_add_worksheet(workbook, "CpltExport.xlsx"); // Map: row number -> length of generated ranges std::map generatedRanges; for (size_t i = 0; i < mArrayGroups.size(); ++i) { auto& info = mArrayGroups[i]; auto& param = params.ArrayGroups[i]; auto iter = generatedRanges.find(i); if (iter != generatedRanges.end()) { int available = iter->second; if (available >= param.size()) { // Current space is enough to fit in this array group, skip continue; } } // Not enough space to fit in this array group, update (or insert) the appropriate amount of generated rows int row = i; int count = param.size(); generatedRanges.try_emplace(row, count); } auto GetOffset = [&](int y) -> int { // std::find_if int verticalOffset = 0; for (auto it = generatedRanges.begin(); it != generatedRanges.end() && it->first < y; ++it) { verticalOffset += it->second; } return verticalOffset; }; auto WriteCell = [&](int row, int col, const TableCell& cell, const char* text) -> void { if (cell.IsPrimaryCell()) { int lastRow = row + cell.SpanY - 1; int lastCol = col + cell.SpanX - 1; // When both `string` and `format` are null, the top-left cell contents are untouched (what we just wrote in the above switch) worksheet_merge_range(worksheet, row, col, lastRow, lastCol, text, nullptr); } else { worksheet_write_string(worksheet, row, col, text, nullptr); } }; // Write/instantiate all array groups for (size_t i = 0; i < mArrayGroups.size(); ++i) { auto& groupInfo = mArrayGroups[i]; auto& groupParams = params.ArrayGroups[i]; int rowCellCount = groupInfo.GetCount(); int rowCount = groupParams.size(); int baseRowIdx = groupInfo.Row + GetOffset(groupInfo.Row); // For each row that would be generated for (int rowIdx = 0; rowIdx < rowCount; ++rowIdx) { auto& row = groupParams[rowIdx]; // For each cell in the row for (int rowCellIdx = 0; rowCellIdx < rowCellCount; ++rowCellIdx) { // TODO support merged cells in array groups worksheet_write_string(worksheet, baseRowIdx + rowIdx, rowCellIdx, row[rowCellIdx].c_str(), nullptr); } } } int tableWidth = GetTableWidth(); int tableHeight = GetTableHeight(); // Write all regular and singular parameter cells for (int y = 0; y < tableHeight; ++y) { for (int x = 0; x < tableWidth; ++x) { auto& cell = GetCell({ x, y }); if (!cell.IsDataHoldingCell()) { continue; } switch (cell.Type) { case TableCell::ConstantCell: { int row = y + GetOffset(y); int col = x; WriteCell(row, col, cell, cell.Content.c_str()); } break; case TableCell::SingularParametricCell: { int row = y + GetOffset(y); int col = x; auto iter = params.SingularCells.find({ x, y }); if (iter != params.SingularCells.end()) { WriteCell(row, col, cell, iter.value().c_str()); } } break; // See loop above that processes whole array groups at the same time case TableCell::ArrayParametricCell: break; } } } return worksheet; } Template::ReadResult TableTemplate::ReadFrom(std::istream& stream) { // TODO return ReadResult::RR_Success; } void TableTemplate::WriteTo(std::ostream& stream) const { // TODO }