Merge pull request #342 from kostasdizas/dev

Added new feature to insert or delete rows / columns
This commit is contained in:
Thomas Fussell 2019-06-11 20:39:58 -04:00 committed by GitHub
commit 7d2a630e00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 497 additions and 0 deletions

View File

@ -283,6 +283,12 @@ public:
index_t index;
};
enum class row_or_col_t : int
{
row,
column
};
/// <summary>
/// Functor for hashing a column.
/// Allows for use of std::unordered_set<column_t, column_hash> and similar.

View File

@ -270,6 +270,26 @@ public:
/// </summary>
void clear_row(row_t row);
/// <summary>
/// Insert empty rows before the given row index
/// </summary>
void insert_rows(row_t row, std::uint32_t amount);
/// <summary>
/// Insert empty columns before the given column index
/// </summary>
void insert_columns(column_t column, std::uint32_t amount);
/// <summary>
/// Delete rows before the given row index
/// </summary>
void delete_rows(row_t row, std::uint32_t amount);
/// <summary>
/// Delete columns before the given column index
/// </summary>
void delete_columns(column_t column, std::uint32_t amount);
// properties
/// <summary>
@ -778,6 +798,12 @@ private:
/// </summary>
void parent(class workbook &wb);
/// <summary>
/// Move cells after index down or right by a given amount. The direction is decided by row_or_col.
/// If reverse is true, the cells will be moved up or left, depending on row_or_col.
/// </summary>
void move_cells(std::uint32_t index, std::uint32_t amount, row_or_col_t row_or_col, bool reverse = false);
/// <summary>
/// The pointer to this sheet's implementation.
/// </summary>

View File

@ -42,7 +42,10 @@
#include <xlnt/worksheet/range_iterator.hpp>
#include <xlnt/worksheet/range_reference.hpp>
#include <xlnt/worksheet/worksheet.hpp>
#include <xlnt/worksheet/row_properties.hpp>
#include <xlnt/worksheet/column_properties.hpp>
#include <detail/constants.hpp>
#include <detail/default_case.hpp>
#include <detail/implementations/cell_impl.hpp>
#include <detail/implementations/workbook_impl.hpp>
#include <detail/implementations/worksheet_impl.hpp>
@ -736,6 +739,182 @@ void worksheet::clear_row(row_t row)
// TODO: garbage collect newly unreferenced resources such as styles?
}
void worksheet::insert_rows(row_t row, std::uint32_t amount)
{
move_cells(row, amount, row_or_col_t::row);
}
void worksheet::insert_columns(column_t column, std::uint32_t amount)
{
move_cells(column.index, amount, row_or_col_t::column);
}
void worksheet::delete_rows(row_t row, std::uint32_t amount)
{
move_cells(row + amount, amount, row_or_col_t::row, true);
}
void worksheet::delete_columns(column_t column, std::uint32_t amount)
{
move_cells(column.index + amount, amount, row_or_col_t::column, true);
}
void worksheet::move_cells(std::uint32_t min_index, std::uint32_t amount, row_or_col_t row_or_col, bool reverse)
{
if (reverse && amount > min_index)
{
throw xlnt::invalid_parameter();
}
if ((!reverse && row_or_col == row_or_col_t::row && min_index > constants::max_row() - amount) ||
(!reverse && row_or_col == row_or_col_t::column && min_index > constants::max_column() - amount))
{
throw xlnt::exception("Cannot move cells as they would be outside the maximum bounds of the spreadsheet");
}
std::vector<detail::cell_impl> cells_to_move;
auto cell_iter = d_->cell_map_.cbegin();
while (cell_iter != d_->cell_map_.cend())
{
std::uint32_t current_index;
switch (row_or_col)
{
case row_or_col_t::row:
current_index = cell_iter->first.row();
break;
case row_or_col_t::column:
current_index = cell_iter->first.column().index;
break;
default:
throw xlnt::unhandled_switch_case();
}
if (current_index >= min_index) // extract cells to be moved
{
auto cell = cell_iter->second;
if (row_or_col == row_or_col_t::row)
{
cell.row_ = reverse ? cell.row_ - amount : cell.row_ + amount;
}
else if (row_or_col == row_or_col_t::column)
{
cell.column_ = reverse ? cell.column_.index - amount: cell.column_.index + amount;
}
cells_to_move.push_back(cell);
cell_iter = d_->cell_map_.erase(cell_iter);
}
else if (reverse && current_index >= min_index - amount) // delete destination cells
{
cell_iter = d_->cell_map_.erase(cell_iter);
}
else // skip other cells
{
++cell_iter;
}
}
for (auto &cell : cells_to_move)
{
d_->cell_map_[cell_reference(cell.column_, cell.row_)] = cell;
}
if (row_or_col == row_or_col_t::row)
{
std::vector<std::pair<row_t, xlnt::row_properties>> properties_to_move;
auto row_prop_iter = d_->row_properties_.cbegin();
while (row_prop_iter != d_->row_properties_.cend())
{
auto current_row = row_prop_iter->first;
if (current_row >= min_index) // extract properties that need to be moved
{
auto tmp_row = reverse ? current_row - amount : current_row + amount;
properties_to_move.push_back({tmp_row, row_prop_iter->second});
row_prop_iter = d_->row_properties_.erase(row_prop_iter);
}
else if (reverse && current_row >= min_index - amount) // clear properties of destination when in reverse
{
row_prop_iter = d_->row_properties_.erase(row_prop_iter);
}
else // skip the rest
{
++row_prop_iter;
}
}
for (const auto &prop : properties_to_move)
{
add_row_properties(prop.first, prop.second);
}
}
else if (row_or_col == row_or_col_t::column)
{
std::vector<std::pair<column_t, xlnt::column_properties>> properties_to_move;
auto col_prop_iter = d_->column_properties_.cbegin();
while (col_prop_iter != d_->column_properties_.cend())
{
auto current_col = col_prop_iter->first.index;
if (current_col >= min_index) // extract properties that need to be moved
{
auto tmp_column = column_t(reverse ? current_col - amount : current_col + amount);
properties_to_move.push_back({tmp_column, col_prop_iter->second});
col_prop_iter = d_->column_properties_.erase(col_prop_iter);
}
else if (reverse && current_col >= min_index - amount) // clear properties of destination when in reverse
{
col_prop_iter = d_->column_properties_.erase(col_prop_iter);
}
else // skip the rest
{
++col_prop_iter;
}
}
for (auto &prop : properties_to_move)
{
add_column_properties(prop.first, prop.second);
}
}
// adjust merged cells
auto shift_reference = [min_index, amount, row_or_col, reverse](cell_reference &ref)
{
auto index = row_or_col == row_or_col_t::row ?
ref.row() :
ref.column_index();
if (index >= min_index)
{
auto new_index = reverse ? index - amount : index + amount;
if (row_or_col == row_or_col_t::row)
{
ref.row(new_index);
}
else if (row_or_col == row_or_col_t::column)
{
ref.column_index(new_index);
}
}
};
for (auto merged_cell = d_->merged_cells_.begin(); merged_cell != d_->merged_cells_.end(); ++merged_cell)
{
cell_reference new_top_left = merged_cell->top_left();
shift_reference(new_top_left);
cell_reference new_bottom_right = merged_cell->bottom_right();
shift_reference(new_bottom_right);
range_reference new_range {new_top_left, new_bottom_right};
if (*merged_cell != new_range)
{
*merged_cell = new_range;
}
}
}
bool worksheet::operator==(const worksheet &other) const
{
return compare(other, true);

View File

@ -104,6 +104,12 @@ public:
register_test(test_clear_cell);
register_test(test_clear_row);
register_test(test_set_title);
register_test(test_insert_rows);
register_test(test_insert_columns);
register_test(test_delete_rows);
register_test(test_delete_columns);
register_test(test_insert_too_many);
register_test(test_insert_delete_moves_merges);
}
void test_new_worksheet()
@ -1259,5 +1265,285 @@ public:
xlnt_assert(ws1_title == ws1.title());
xlnt_assert(ws2_title == ws2.title());
}
void test_insert_rows()
{
xlnt::workbook wb;
auto ws = wb.active_sheet();
// set up a 2x2 grid
ws.cell("A1").value("A1");
ws.cell("A2").value("A2");
ws.cell("B1").value("B1");
ws.cell("B2").value("B2");
xlnt::row_properties row_prop;
row_prop.height = 1;
ws.add_row_properties(1, row_prop);
row_prop.height = 2;
ws.add_row_properties(2, row_prop);
xlnt::column_properties col_prop;
col_prop.width = 1;
ws.add_column_properties(1, col_prop);
col_prop.width = 2;
ws.add_column_properties(2, col_prop);
// insert
ws.insert_rows(2, 2);
// first row should be unchanged
xlnt_assert(ws.cell("A1").has_value());
xlnt_assert(ws.cell("B1").has_value());
xlnt_assert_equals(ws.cell("A1").value<std::string>(), "A1");
xlnt_assert_equals(ws.cell("B1").value<std::string>(), "B1");
xlnt_assert_equals(ws.row_properties(1).height, 1);
// second and third rows should be empty
xlnt_assert(!ws.cell("A2").has_value());
xlnt_assert(!ws.cell("B2").has_value());
xlnt_assert(!ws.has_row_properties(2));
xlnt_assert(!ws.cell("A3").has_value());
xlnt_assert(!ws.cell("B3").has_value());
xlnt_assert(!ws.has_row_properties(3));
// fourth row should have the contents and properties of the second
xlnt_assert(ws.cell("A4").has_value());
xlnt_assert(ws.cell("B4").has_value());
xlnt_assert_equals(ws.cell("A4").value<std::string>(), "A2");
xlnt_assert_equals(ws.cell("B4").value<std::string>(), "B2");
xlnt_assert_equals(ws.row_properties(4).height, 2);
// column properties should remain unchanged
xlnt_assert(ws.has_column_properties(1));
xlnt_assert(ws.has_column_properties(2));
}
void test_insert_columns()
{
xlnt::workbook wb;
auto ws = wb.active_sheet();
// set up a 2x2 grid
ws.cell("A1").value("A1");
ws.cell("A2").value("A2");
ws.cell("B1").value("B1");
ws.cell("B2").value("B2");
xlnt::row_properties row_prop;
row_prop.height = 1;
ws.add_row_properties(1, row_prop);
row_prop.height = 2;
ws.add_row_properties(2, row_prop);
xlnt::column_properties col_prop;
col_prop.width = 1;
ws.add_column_properties(1, col_prop);
col_prop.width = 2;
ws.add_column_properties(2, col_prop);
// insert
ws.insert_columns(2, 2);
// first column should be unchanged
xlnt_assert(ws.cell("A1").has_value());
xlnt_assert(ws.cell("A2").has_value());
xlnt_assert_equals(ws.cell("A1").value<std::string>(), "A1");
xlnt_assert_equals(ws.cell("A2").value<std::string>(), "A2");
xlnt_assert_equals(ws.column_properties(1).width, 1);
// second and third columns should be empty
xlnt_assert(!ws.cell("B1").has_value());
xlnt_assert(!ws.cell("B2").has_value());
xlnt_assert(!ws.has_column_properties(2));
xlnt_assert(!ws.cell("C1").has_value());
xlnt_assert(!ws.cell("C2").has_value());
xlnt_assert(!ws.has_column_properties(3));
// fourth column should have the contents and properties of the second
xlnt_assert(ws.cell("D1").has_value());
xlnt_assert(ws.cell("D2").has_value());
xlnt_assert_equals(ws.cell("D1").value<std::string>(), "B1");
xlnt_assert_equals(ws.cell("D2").value<std::string>(), "B2");
xlnt_assert_equals(ws.column_properties(4).width, 2);
// row properties should remain unchanged
xlnt_assert_equals(ws.row_properties(1).height, 1);
xlnt_assert_equals(ws.row_properties(2).height, 2);
}
void test_delete_rows()
{
xlnt::workbook wb;
auto ws = wb.active_sheet();
// set up a 4x4 grid
for (int i = 1; i <= 4; ++i)
{
for (int j = 1; j <= 4; ++j)
{
ws.cell(xlnt::cell_reference(i, j)).value(xlnt::cell_reference(i, j).to_string());
}
xlnt::row_properties row_prop;
row_prop.height = i;
ws.add_row_properties(i, row_prop);
xlnt::column_properties col_prop;
col_prop.width = i;
ws.add_column_properties(i, col_prop);
}
// delete
ws.delete_rows(2, 2);
// first row should remain unchanged
xlnt_assert_equals(ws.cell("A1").value<std::string>(), "A1");
xlnt_assert_equals(ws.cell("B1").value<std::string>(), "B1");
xlnt_assert_equals(ws.cell("C1").value<std::string>(), "C1");
xlnt_assert_equals(ws.cell("D1").value<std::string>(), "D1");
xlnt_assert(ws.has_row_properties(1));
xlnt_assert_equals(ws.row_properties(1).height, 1);
// second row should have the contents and properties of the fourth
xlnt_assert_equals(ws.cell("A2").value<std::string>(), "A4");
xlnt_assert_equals(ws.cell("B2").value<std::string>(), "B4");
xlnt_assert_equals(ws.cell("C2").value<std::string>(), "C4");
xlnt_assert_equals(ws.cell("D2").value<std::string>(), "D4");
xlnt_assert(ws.has_row_properties(2));
xlnt_assert_equals(ws.row_properties(2).height, 4);
// third and fourth rows should be empty
auto empty_range = ws.range("A3:D4");
for (auto empty_row : empty_range)
{
for (auto empty_cell : empty_row)
{
xlnt_assert(!empty_cell.has_value());
}
}
xlnt_assert(!ws.has_row_properties(3));
xlnt_assert(!ws.has_row_properties(4));
// column properties should remain unchanged
for (int i = 1; i <= 4; ++i)
{
xlnt_assert(ws.has_column_properties(i));
xlnt_assert_equals(ws.column_properties(i).width, i);
}
}
void test_delete_columns()
{
xlnt::workbook wb;
auto ws = wb.active_sheet();
// set up a 4x4 grid
for (int i = 1; i <= 4; ++i)
{
for (int j = 1; j <= 4; ++j)
{
ws.cell(xlnt::cell_reference(i, j)).value(xlnt::cell_reference(i, j).to_string());
}
xlnt::row_properties row_prop;
row_prop.height = i;
ws.add_row_properties(i, row_prop);
xlnt::column_properties col_prop;
col_prop.width = i;
ws.add_column_properties(i, col_prop);
}
// delete
ws.delete_columns(2, 2);
// first column should remain unchanged
xlnt_assert_equals(ws.cell("A1").value<std::string>(), "A1");
xlnt_assert_equals(ws.cell("A2").value<std::string>(), "A2");
xlnt_assert_equals(ws.cell("A3").value<std::string>(), "A3");
xlnt_assert_equals(ws.cell("A4").value<std::string>(), "A4");
xlnt_assert(ws.has_column_properties("A"));
xlnt_assert_equals(ws.column_properties("A").width.get(), 1);
// second column should have the contents and properties of the fourth
xlnt_assert_equals(ws.cell("B1").value<std::string>(), "D1");
xlnt_assert_equals(ws.cell("B2").value<std::string>(), "D2");
xlnt_assert_equals(ws.cell("B3").value<std::string>(), "D3");
xlnt_assert_equals(ws.cell("B4").value<std::string>(), "D4");
xlnt_assert(ws.has_column_properties("B"));
xlnt_assert_equals(ws.column_properties("B").width.get(), 4);
// third and fourth columns should be empty
auto empty_range = ws.range("C1:D4");
for (auto empty_row : empty_range)
{
for (auto empty_cell : empty_row)
{
xlnt_assert(!empty_cell.has_value());
}
}
xlnt_assert(!ws.has_column_properties("C"));
xlnt_assert(!ws.has_column_properties("D"));
// row properties should remain unchanged
for (int i = 1; i <= 4; ++i)
{
xlnt_assert(ws.has_row_properties(i));
xlnt_assert_equals(ws.row_properties(i).height, i);
}
}
void test_insert_too_many()
{
xlnt::workbook wb;
auto ws = wb.active_sheet();
xlnt_assert_throws(ws.insert_rows(10, 4294967290),
xlnt::exception);
}
void test_insert_delete_moves_merges()
{
xlnt::workbook wb;
auto ws = wb.active_sheet();
ws.merge_cells("A1:A2");
ws.merge_cells("B2:B3");
ws.merge_cells("C3:C4");
ws.merge_cells("A5:B5");
ws.merge_cells("B6:C6");
ws.merge_cells("C7:D7");
{
ws.insert_rows(3, 3);
ws.insert_columns(3, 3);
auto merged = ws.merged_ranges();
std::vector<xlnt::range_reference> expected =
{
xlnt::range_reference { "A1:A2" }, // stays
xlnt::range_reference { "B2:B6" }, // expands
xlnt::range_reference { "F6:F7" }, // shifts
xlnt::range_reference { "A8:B8" }, // stays (shifted down)
xlnt::range_reference { "B9:F9" }, // expands (shifted down)
xlnt::range_reference { "F10:G10" }, // shifts (shifted down)
};
xlnt_assert_equals(merged, expected);
}
{
ws.delete_rows(4, 2);
ws.delete_columns(4, 2);
auto merged = ws.merged_ranges();
std::vector<xlnt::range_reference> expected =
{
xlnt::range_reference { "A1:A2" }, // stays
xlnt::range_reference { "B2:B4" }, // expands
xlnt::range_reference { "D4:D5" }, // shifts
xlnt::range_reference { "A6:B6" }, // stays (shifted down)
xlnt::range_reference { "B7:D7" }, // expands (shifted down)
xlnt::range_reference { "D8:E8" }, // shifts (shifted down)
};
xlnt_assert_equals(merged, expected);
}
}
};
static worksheet_test_suite x;