diff --git a/include/xlnt/cell/index_types.hpp b/include/xlnt/cell/index_types.hpp index 129a5ee5..b7f47a5d 100644 --- a/include/xlnt/cell/index_types.hpp +++ b/include/xlnt/cell/index_types.hpp @@ -283,6 +283,12 @@ public: index_t index; }; +enum class row_or_col_t : int +{ + row, + column +}; + /// /// Functor for hashing a column. /// Allows for use of std::unordered_set and similar. diff --git a/include/xlnt/worksheet/worksheet.hpp b/include/xlnt/worksheet/worksheet.hpp index ed460992..69b998e7 100644 --- a/include/xlnt/worksheet/worksheet.hpp +++ b/include/xlnt/worksheet/worksheet.hpp @@ -270,6 +270,26 @@ public: /// void clear_row(row_t row); + /// + /// Insert empty rows before the given row index + /// + void insert_rows(row_t row, std::uint32_t amount); + + /// + /// Insert empty columns before the given column index + /// + void insert_columns(column_t column, std::uint32_t amount); + + /// + /// Delete rows before the given row index + /// + void delete_rows(row_t row, std::uint32_t amount); + + /// + /// Delete columns before the given column index + /// + void delete_columns(column_t column, std::uint32_t amount); + // properties /// @@ -778,6 +798,12 @@ private: /// void parent(class workbook &wb); + /// + /// 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. + /// + void move_cells(std::uint32_t index, std::uint32_t amount, row_or_col_t row_or_col, bool reverse = false); + /// /// The pointer to this sheet's implementation. /// diff --git a/source/cell/cell.cpp b/source/cell/cell.cpp index f63cb06f..25007f55 100644 --- a/source/cell/cell.cpp +++ b/source/cell/cell.cpp @@ -371,7 +371,7 @@ hyperlink cell::hyperlink() const void cell::hyperlink(const std::string &url, const std::string &display) { - if (url.empty() || std::find(url.begin(), url.end(), ':') == url.end()) + if (url.empty()) { throw invalid_parameter(); } @@ -857,6 +857,12 @@ void cell::clear_style() void cell::style(const class style &new_style) { auto new_format = has_format() ? format() : workbook().create_format(); + + new_format.border(new_style.border()); + new_format.fill(new_style.fill()); + new_format.font(new_style.font()); + new_format.number_format(new_style.number_format()); + format(new_format.style(new_style)); } diff --git a/source/detail/implementations/stylesheet.hpp b/source/detail/implementations/stylesheet.hpp index 0ec45161..67ffa063 100644 --- a/source/detail/implementations/stylesheet.hpp +++ b/source/detail/implementations/stylesheet.hpp @@ -51,12 +51,7 @@ struct stylesheet impl.parent = this; impl.id = format_impls.size() - 1; - - impl.border_id = 0; - impl.fill_id = 0; - impl.font_id = 0; - impl.number_format_id = 0; - + impl.references = default_format ? 1 : 0; return xlnt::format(&impl); diff --git a/source/detail/number_format/number_formatter.cpp b/source/detail/number_format/number_formatter.cpp index 5b0d5d49..284412d1 100644 --- a/source/detail/number_format/number_formatter.cpp +++ b/source/detail/number_format/number_formatter.cpp @@ -1673,13 +1673,13 @@ std::string number_formatter::format_number(const format_code &format, double nu case template_part::template_type::day_abbreviation: { - result.append(day_names->at(static_cast(dt.weekday()) - 1).substr(0, 3)); + result.append(day_names->at(static_cast(dt.weekday())).substr(0, 3)); break; } case template_part::template_type::day_name: { - result.append(day_names->at(static_cast(dt.weekday()) - 1)); + result.append(day_names->at(static_cast(dt.weekday()))); break; } } diff --git a/source/detail/serialization/xlsx_consumer.cpp b/source/detail/serialization/xlsx_consumer.cpp index 0112498c..0ec6f36d 100644 --- a/source/detail/serialization/xlsx_consumer.cpp +++ b/source/detail/serialization/xlsx_consumer.cpp @@ -2349,7 +2349,7 @@ void xlsx_consumer::read_stylesheet() } record.first.border_id = parser().attribute_present("borderId") ? parser().attribute("borderId") - : 0; + : optional(); if (parser().attribute_present("applyFill")) { @@ -2357,7 +2357,7 @@ void xlsx_consumer::read_stylesheet() } record.first.fill_id = parser().attribute_present("fillId") ? parser().attribute("fillId") - : 0; + : optional(); if (parser().attribute_present("applyFont")) { @@ -2365,7 +2365,7 @@ void xlsx_consumer::read_stylesheet() } record.first.font_id = parser().attribute_present("fontId") ? parser().attribute("fontId") - : 0; + : optional(); if (parser().attribute_present("applyNumberFormat")) { @@ -2373,7 +2373,7 @@ void xlsx_consumer::read_stylesheet() } record.first.number_format_id = parser().attribute_present("numFmtId") ? parser().attribute("numFmtId") - : 0; + : optional(); auto apply_alignment_present = parser().attribute_present("applyAlignment"); if (apply_alignment_present) diff --git a/source/utils/date.cpp b/source/utils/date.cpp index df98a35d..6b6302f8 100644 --- a/source/utils/date.cpp +++ b/source/utils/date.cpp @@ -126,18 +126,10 @@ date date::today() int date::weekday() const { - auto year_temp = (month == 1 || month == 2) ? year - 1 : year; - auto month_temp = month == 1 ? 13 : month == 2 ? 14 : month; - auto day_temp = day + 1; + std::tm tm {0, 0, 0, day, month - 1, year - 1900}; + std::time_t time = std::mktime(&tm); - auto days = day_temp + static_cast(13 * (month_temp + 1) / 5.0) + (year_temp % 100) - + static_cast((year_temp % 100) / 4.0); - auto gregorian = days + static_cast(year_temp / 400.0) - 2 * year_temp / 100; - auto julian = days + 5 - year_temp / 100; - - int weekday = (year_temp > 1582 ? gregorian : julian) % 7; - - return weekday == 0 ? 7 : weekday; + return safe_localtime(time).tm_wday; } } // namespace xlnt diff --git a/source/workbook/workbook.cpp b/source/workbook/workbook.cpp index fcea0b12..dae61446 100644 --- a/source/workbook/workbook.cpp +++ b/source/workbook/workbook.cpp @@ -1617,9 +1617,9 @@ struct rel_id_sorter bool operator()(const xlnt::relationship &lhs, const xlnt::relationship &rhs) { // format is rTd - if (lhs.id().size() < rhs.id().size()) // a number with more digits will be larger + if (lhs.id().size() != rhs.id().size()) // a number with more digits will be larger { - return true; + return lhs.id().size() < rhs.id().size(); } return lhs.id() < rhs.id(); } diff --git a/source/worksheet/worksheet.cpp b/source/worksheet/worksheet.cpp index 33386f84..23c9ea7c 100644 --- a/source/worksheet/worksheet.cpp +++ b/source/worksheet/worksheet.cpp @@ -42,7 +42,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -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 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> 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> 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); diff --git a/tests/cell/cell_test_suite.cpp b/tests/cell/cell_test_suite.cpp index 038fbe20..73de12cf 100644 --- a/tests/cell/cell_test_suite.cpp +++ b/tests/cell/cell_test_suite.cpp @@ -688,7 +688,6 @@ private: xlnt_assert(!cell.has_hyperlink()); xlnt_assert_throws(cell.hyperlink(), xlnt::invalid_attribute); - xlnt_assert_throws(cell.hyperlink("notaurl"), xlnt::invalid_parameter); xlnt_assert_throws(cell.hyperlink(""), xlnt::invalid_parameter); // link without optional display const std::string link1("http://example.com"); @@ -708,6 +707,13 @@ private: xlnt_assert_equals(cell.hyperlink().url(), link2); xlnt_assert_equals(cell.hyperlink().relationship().target().to_string(), link2); xlnt_assert_equals(cell.hyperlink().display(), display_txt); + // relative (local) url + const std::string local("../test_local"); + cell.hyperlink(local); + xlnt_assert(cell.has_hyperlink()); + xlnt_assert(cell.hyperlink().external()); + xlnt_assert_equals(cell.hyperlink().url(), local); + xlnt_assert_equals(cell.hyperlink().relationship().target().to_string(), local); // value int cell_test_val = 123; cell.value(cell_test_val); diff --git a/tests/data/10_comments_hyperlinks_formulae.xlsx b/tests/data/10_comments_hyperlinks_formulae.xlsx index c989dc5f..142d3d0b 100644 Binary files a/tests/data/10_comments_hyperlinks_formulae.xlsx and b/tests/data/10_comments_hyperlinks_formulae.xlsx differ diff --git a/tests/styles/number_format_test_suite.cpp b/tests/styles/number_format_test_suite.cpp index 0b7e291e..c510e594 100644 --- a/tests/styles/number_format_test_suite.cpp +++ b/tests/styles/number_format_test_suite.cpp @@ -37,12 +37,12 @@ public: { register_test(test_basic); register_test(test_simple_format); + register_test(test_bad_date_format); register_test(test_simple_date); register_test(test_short_month); register_test(test_month_abbreviation); register_test(test_month_name); register_test(test_basic); - register_test(test_simple_format); register_test(test_upper_case_date); register_test(test_simple_date); register_test(test_short_day); @@ -155,6 +155,46 @@ public: xlnt_assert_equals(formatted, "zero0"); } + void test_bad_date_format() + { + auto date = xlnt::date(2016, 6, 18); + auto date_number = date.to_number(xlnt::calendar::windows_1900); + + xlnt::number_format nf; + + nf.format_string("[x]"); + xlnt_assert_throws(nf.format(date_number, xlnt::calendar::windows_1900), + std::runtime_error); + + nf.format_string("mmmmmm"); + xlnt_assert_throws(nf.format(date_number, xlnt::calendar::windows_1900), + std::runtime_error); + + nf.format_string("ddddd"); + xlnt_assert_throws(nf.format(date_number, xlnt::calendar::windows_1900), + std::runtime_error); + + nf.format_string("yyy"); + xlnt_assert_throws(nf.format(date_number, xlnt::calendar::windows_1900), + std::runtime_error); + + nf.format_string("hhh"); + xlnt_assert_throws(nf.format(date_number, xlnt::calendar::windows_1900), + std::runtime_error); + + nf.format_string("sss"); + xlnt_assert_throws(nf.format(date_number, xlnt::calendar::windows_1900), + std::runtime_error); + + nf.format_string("AA"); + xlnt_assert_throws(nf.format(date_number, xlnt::calendar::windows_1900), + std::runtime_error); + + nf.format_string("q"); + xlnt_assert_throws(nf.format(date_number, xlnt::calendar::windows_1900), + std::runtime_error); + } + void test_upper_case_date() { auto date = xlnt::date(2016, 6, 18); @@ -259,7 +299,7 @@ public: nf.format_string("dddd"); auto formatted = nf.format(date_number, xlnt::calendar::windows_1900); - xlnt_assert_equals(formatted, "Sunday"); + xlnt_assert_equals(formatted, "Saturday"); } void test_day_abbreviation() @@ -271,7 +311,7 @@ public: nf.format_string("ddd"); auto formatted = nf.format(date_number, xlnt::calendar::windows_1900); - xlnt_assert_equals(formatted, "Sun"); + xlnt_assert_equals(formatted, "Sat"); } void test_month_letter() diff --git a/tests/utils/datetime_test_suite.cpp b/tests/utils/datetime_test_suite.cpp index 85e5f186..22e8266c 100644 --- a/tests/utils/datetime_test_suite.cpp +++ b/tests/utils/datetime_test_suite.cpp @@ -40,6 +40,7 @@ public: register_test(test_early_date); register_test(test_mac_calendar); register_test(test_operators); + register_test(test_weekday); } void test_from_string() @@ -113,5 +114,12 @@ public: xlnt_assert_equals(d1, d2); xlnt_assert_differs(d1, d3); } + + void test_weekday() + { + xlnt_assert_equals(xlnt::date(2000, 1, 1).weekday(), 6); + xlnt_assert_equals(xlnt::date(2016, 7, 15).weekday(), 5); + xlnt_assert_equals(xlnt::date(2018, 10, 29).weekday(), 1); + } }; static datetime_test_suite x; \ No newline at end of file diff --git a/tests/worksheet/worksheet_test_suite.cpp b/tests/worksheet/worksheet_test_suite.cpp index 24dad90c..e587c134 100644 --- a/tests/worksheet/worksheet_test_suite.cpp +++ b/tests/worksheet/worksheet_test_suite.cpp @@ -105,6 +105,12 @@ public: register_test(test_clear_row); register_test(test_set_title); register_test(test_phonetics); + 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() @@ -1283,5 +1289,285 @@ public: xlnt_assert_equals(ws2.cell("B1").phonetics_visible(), true); xlnt_assert_equals(ws2.cell("C1").phonetics_visible(), false); } + + 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(), "A1"); + xlnt_assert_equals(ws.cell("B1").value(), "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(), "A2"); + xlnt_assert_equals(ws.cell("B4").value(), "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(), "A1"); + xlnt_assert_equals(ws.cell("A2").value(), "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(), "B1"); + xlnt_assert_equals(ws.cell("D2").value(), "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(), "A1"); + xlnt_assert_equals(ws.cell("B1").value(), "B1"); + xlnt_assert_equals(ws.cell("C1").value(), "C1"); + xlnt_assert_equals(ws.cell("D1").value(), "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(), "A4"); + xlnt_assert_equals(ws.cell("B2").value(), "B4"); + xlnt_assert_equals(ws.cell("C2").value(), "C4"); + xlnt_assert_equals(ws.cell("D2").value(), "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(), "A1"); + xlnt_assert_equals(ws.cell("A2").value(), "A2"); + xlnt_assert_equals(ws.cell("A3").value(), "A3"); + xlnt_assert_equals(ws.cell("A4").value(), "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(), "D1"); + xlnt_assert_equals(ws.cell("B2").value(), "D2"); + xlnt_assert_equals(ws.cell("B3").value(), "D3"); + xlnt_assert_equals(ws.cell("B4").value(), "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 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 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; \ No newline at end of file