#include "TableTemplate.hpp" #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 TableCellArrayGroup::GetLeftCell() const { return { Row, LeftCell }; } Vec2i TableCellArrayGroup::GetRightCell() const { return { Row, RightCell }; } int TableCellArrayGroup::GetCount() const { return RightCell - LeftCell + 1; } TableInstanciationParameters::TableInstanciationParameters(const TableTemplate& table) : mTable{ &table } { } TableInstanciationParameters& TableInstanciationParameters::ResetTable(const TableTemplate& newTable) { mTable = &newTable; return *this; } TableInstanciationParameters TableInstanciationParameters::RebindTable(const TableTemplate& newTable) const { TableInstanciationParameters result(newTable); result.SingularCells = this->SingularCells; result.ArrayGroups = this->ArrayGroups; return result; } const TableTemplate& TableInstanciationParameters::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(); int yEnd = std::min(tableHeight, newHeight); int xEnd = std::min(tableWidth, newWidth); for (int y = 0; y < yEnd; ++y) { if (y >= tableHeight) { for (int x = 0; x < xEnd; ++x) { cells.push_back(TableCell{}); } continue; } for (int x = 0; x < xEnd; ++x) { if (x >= tableWidth) { cells.push_back(TableCell{}); } else { auto& cell = GetCell({ x, y }); cells.push_back(std::move(cell)); } } } mCells = std::move(cells); } 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)); } 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 TableInstanciationParameters& params) const { auto workbook = workbook_new("Table.xlsx"); InstantiateToExcelWorksheet(workbook, params); return workbook; } lxw_worksheet* TableTemplate::InstantiateToExcelWorksheet(lxw_workbook* workbook, const TableInstanciationParameters& 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 }