diff --git a/include/xlnt/worksheet/worksheet.hpp b/include/xlnt/worksheet/worksheet.hpp index aca470e71..018d8fc08 100644 --- a/include/xlnt/worksheet/worksheet.hpp +++ b/include/xlnt/worksheet/worksheet.hpp @@ -370,11 +370,21 @@ class XLNT_API worksheet /// row_t lowest_row() const; + /// + /// Returns the row of the first non-empty cell or lowest row with properties in the worksheet. + /// + row_t lowest_row_or_props() const; + /// /// Returns the row of the last non-empty cell in the worksheet. /// row_t highest_row() const; + /// + /// Returns the row of the last non-empty cell or highest row with properties in the worksheet. + /// + row_t highest_row_or_props() const; + /// /// Returns the row directly below the last non-empty cell in the worksheet. /// @@ -385,11 +395,21 @@ class XLNT_API worksheet /// column_t lowest_column() const; + /// + /// Returns the column of the first non-empty cell or lowest column with properties in the worksheet. + /// + column_t lowest_column_or_props() const; + /// /// Returns the column of the last non-empty cell in the worksheet. /// column_t highest_column() const; + /// + /// Returns the column of the last non-empty cell or highest column with properties in the worksheet. + /// + column_t highest_column_or_props() const; + /// /// Returns a range_reference pointing to the full range of non-empty cells in the worksheet. /// diff --git a/source/detail/serialization/xlsx_producer.cpp b/source/detail/serialization/xlsx_producer.cpp index dfbe3bb81..fd660d5d0 100644 --- a/source/detail/serialization/xlsx_producer.cpp +++ b/source/detail/serialization/xlsx_producer.cpp @@ -2030,8 +2030,9 @@ void xlsx_producer::write_worksheet(const relationship &rel) write_start_element(xmlns, "dimension"); const auto dimension = ws.calculate_dimension(); - write_attribute( - "ref", dimension.is_single_cell() ? dimension.top_left().to_string() : dimension.to_string()); + write_attribute("ref", dimension.is_single_cell() + ? dimension.top_left().to_string() + : dimension.to_string()); write_end_element(xmlns, "dimension"); if (ws.has_view()) @@ -2123,7 +2124,7 @@ void xlsx_producer::write_worksheet(const relationship &rel) { write_start_element(xmlns, "cols"); - for (auto column = ws.lowest_column(); column <= ws.highest_column(); column++) + for (auto column = ws.lowest_column_or_props(); column <= ws.highest_column_or_props(); column++) { if (!ws.has_column_properties(column)) continue; @@ -2172,16 +2173,20 @@ void xlsx_producer::write_worksheet(const relationship &rel) write_start_element(xmlns, "sheetData"); - for (auto row : ws.rows()) + for (auto row = ws.lowest_row_or_props(); row <= ws.highest_row_or_props(); ++row) { - auto min = static_cast(row.length()); - xlnt::row_t max = 0; + auto first_column = constants::max_column(); + auto last_column = constants::min_column(); bool any_non_null = false; - for (auto cell : row) + for (auto column = dimension.top_left().column(); column <= dimension.bottom_right().column(); ++column) { - min = std::min(min, cell.column().index); - max = std::max(max, cell.column().index); + if (!ws.has_cell(cell_reference(column, row))) continue; + + auto cell = ws.cell(cell_reference(column, row)); + + first_column = std::min(first_column, cell.column()); + last_column = std::max(last_column, cell.column()); if (!cell.garbage_collectible()) { @@ -2189,19 +2194,20 @@ void xlsx_producer::write_worksheet(const relationship &rel) } } - if (!any_non_null) - { - continue; - } + if (!any_non_null && !ws.has_row_properties(row)) continue; write_start_element(xmlns, "row"); + write_attribute("r", row); - write_attribute("r", row.front().row()); - write_attribute("spans", std::to_string(min) + ":" + std::to_string(max)); + if (any_non_null) + { + auto span_string = std::to_string(first_column.index) + ":" + std::to_string(last_column.index); + write_attribute("spans", span_string); + } - if (ws.has_row_properties(row.front().row())) + if (ws.has_row_properties(row)) { - const auto &props = ws.row_properties(row.front().row()); + const auto &props = ws.row_properties(row); if (props.custom_height) { @@ -2228,130 +2234,137 @@ void xlsx_producer::write_worksheet(const relationship &rel) } } - for (auto cell : row) // CT_Cell + if (any_non_null) { - if (cell.garbage_collectible()) continue; + for (auto column = dimension.top_left().column(); column <= dimension.bottom_right().column(); ++column) + { + if (!ws.has_cell(cell_reference(column, row))) continue; - // record data about the cell needed later + auto cell = ws.cell(cell_reference(column, row)); - if (cell.has_comment()) - { - cells_with_comments.push_back(cell.reference()); - } + if (cell.garbage_collectible()) continue; - if (cell.has_hyperlink()) - { - hyperlink_references[cell.reference().to_string()] = reverse_hyperlink_references[cell.hyperlink()]; - } + // record data about the cell needed later - write_start_element(xmlns, "c"); + if (cell.has_comment()) + { + cells_with_comments.push_back(cell.reference()); + } - // begin cell attributes + if (cell.has_hyperlink()) + { + hyperlink_references[cell.reference().to_string()] = reverse_hyperlink_references[cell.hyperlink()]; + } - write_attribute("r", cell.reference().to_string()); + write_start_element(xmlns, "c"); - if (cell.has_format()) - { - write_attribute("s", cell.format().d_->id); - } + // begin cell attributes - switch (cell.data_type()) - { - case cell::type::empty: - break; + write_attribute("r", cell.reference().to_string()); - case cell::type::boolean: - write_attribute("t", "b"); - break; + if (cell.has_format()) + { + write_attribute("s", cell.format().d_->id); + } - case cell::type::date: - write_attribute("t", "d"); - break; + switch (cell.data_type()) + { + case cell::type::empty: + break; - case cell::type::error: - write_attribute("t", "e"); - break; + case cell::type::boolean: + write_attribute("t", "b"); + break; - case cell::type::inline_string: - write_attribute("t", "inlineStr"); - break; + case cell::type::date: + write_attribute("t", "d"); + break; - case cell::type::number: - write_attribute("t", "n"); - break; + case cell::type::error: + write_attribute("t", "e"); + break; - case cell::type::shared_string: - write_attribute("t", "s"); - break; + case cell::type::inline_string: + write_attribute("t", "inlineStr"); + break; - case cell::type::formula_string: - write_attribute("t", "str"); - break; - } + case cell::type::number: + write_attribute("t", "n"); + break; - //write_attribute("cm", ""); - //write_attribute("vm", ""); - //write_attribute("ph", ""); + case cell::type::shared_string: + write_attribute("t", "s"); + break; - // begin child elements + case cell::type::formula_string: + write_attribute("t", "str"); + break; + } - if (cell.has_formula()) - { - write_element(xmlns, "f", cell.formula()); - } + //write_attribute("cm", ""); + //write_attribute("vm", ""); + //write_attribute("ph", ""); - switch (cell.data_type()) - { - case cell::type::empty: - break; + // begin child elements - case cell::type::boolean: - write_element(xmlns, "v", write_bool(cell.value())); - break; + if (cell.has_formula()) + { + write_element(xmlns, "f", cell.formula()); + } - case cell::type::date: - write_element(xmlns, "v", cell.value()); - break; + switch (cell.data_type()) + { + case cell::type::empty: + break; - case cell::type::error: - write_element(xmlns, "v", cell.value()); - break; + case cell::type::boolean: + write_element(xmlns, "v", write_bool(cell.value())); + break; - case cell::type::inline_string: - write_start_element(xmlns, "is"); - // TODO: make a write_rich_text method and use that here - write_element(xmlns, "t", cell.value()); - write_end_element(xmlns, "is"); - break; + case cell::type::date: + write_element(xmlns, "v", cell.value()); + break; - case cell::type::number: - write_start_element(xmlns, "v"); + case cell::type::error: + write_element(xmlns, "v", cell.value()); + break; - if (is_integral(cell.value())) - { - write_characters(static_cast(cell.value())); - } - else - { - std::stringstream ss; - ss.precision(20); - ss << cell.value(); - write_characters(ss.str()); - } + case cell::type::inline_string: + write_start_element(xmlns, "is"); + // TODO: make a write_rich_text method and use that here + write_element(xmlns, "t", cell.value()); + write_end_element(xmlns, "is"); + break; - write_end_element(xmlns, "v"); - break; + case cell::type::number: + write_start_element(xmlns, "v"); - case cell::type::shared_string: - write_element(xmlns, "v", static_cast(cell.d_->value_numeric_)); - break; + if (is_integral(cell.value())) + { + write_characters(static_cast(cell.value())); + } + else + { + std::stringstream ss; + ss.precision(20); + ss << cell.value(); + write_characters(ss.str()); + } - case cell::type::formula_string: - write_element(xmlns, "v", cell.value()); - break; - } + write_end_element(xmlns, "v"); + break; + + case cell::type::shared_string: + write_element(xmlns, "v", static_cast(cell.d_->value_numeric_)); + break; - write_end_element(xmlns, "c"); + case cell::type::formula_string: + write_element(xmlns, "v", cell.value()); + break; + } + + write_end_element(xmlns, "c"); + } } write_end_element(xmlns, "row"); diff --git a/source/worksheet/worksheet.cpp b/source/worksheet/worksheet.cpp index 7078cba27..830d3eb46 100644 --- a/source/worksheet/worksheet.cpp +++ b/source/worksheet/worksheet.cpp @@ -481,7 +481,7 @@ column_t worksheet::lowest_column() const return constants::min_column(); } - column_t lowest = constants::max_column(); + auto lowest = constants::max_column(); for (auto &row : d_->cell_map_) { @@ -494,6 +494,23 @@ column_t worksheet::lowest_column() const return lowest; } +column_t worksheet::lowest_column_or_props() const +{ + auto lowest = lowest_column(); + + if (d_->cell_map_.empty() && !d_->column_properties_.empty()) + { + lowest = d_->column_properties_.begin()->first; + } + + for (auto &props : d_->column_properties_) + { + lowest = std::min(lowest, props.first); + } + + return lowest; +} + row_t worksheet::lowest_row() const { if (d_->cell_map_.empty()) @@ -501,7 +518,7 @@ row_t worksheet::lowest_row() const return constants::min_row(); } - row_t lowest = constants::max_row(); + auto lowest = constants::max_row(); for (auto &row : d_->cell_map_) { @@ -511,9 +528,26 @@ row_t worksheet::lowest_row() const return lowest; } +row_t worksheet::lowest_row_or_props() const +{ + auto lowest = lowest_row(); + + if (d_->cell_map_.empty() && !d_->row_properties_.empty()) + { + lowest = d_->row_properties_.begin()->first; + } + + for (auto &props : d_->row_properties_) + { + lowest = std::min(lowest, props.first); + } + + return lowest; +} + row_t worksheet::highest_row() const { - row_t highest = constants::min_row(); + auto highest = constants::min_row(); for (auto &row : d_->cell_map_) { @@ -523,9 +557,26 @@ row_t worksheet::highest_row() const return highest; } +row_t worksheet::highest_row_or_props() const +{ + auto highest = highest_row(); + + if (d_->cell_map_.empty() && !d_->row_properties_.empty()) + { + highest = d_->row_properties_.begin()->first; + } + + for (auto &props : d_->row_properties_) + { + highest = std::max(highest, props.first); + } + + return highest; +} + column_t worksheet::highest_column() const { - column_t highest = constants::min_column(); + auto highest = constants::min_column(); for (auto &row : d_->cell_map_) { @@ -538,6 +589,23 @@ column_t worksheet::highest_column() const return highest; } +column_t worksheet::highest_column_or_props() const +{ + auto highest = highest_column(); + + if (d_->cell_map_.empty() && !d_->column_properties_.empty()) + { + highest = d_->column_properties_.begin()->first; + } + + for (auto &props : d_->column_properties_) + { + highest = std::max(highest, props.first); + } + + return highest; +} + range_reference worksheet::calculate_dimension() const { return range_reference(lowest_column(), lowest_row(), highest_column(), highest_row()); diff --git a/tests/data/13_custom_heights_widths.xlsx b/tests/data/13_custom_heights_widths.xlsx index 13d7a54d5..875edd538 100644 Binary files a/tests/data/13_custom_heights_widths.xlsx and b/tests/data/13_custom_heights_widths.xlsx differ diff --git a/tests/workbook/serialization_test_suite.hpp b/tests/workbook/serialization_test_suite.hpp index 89ccbf00a..da4db5aef 100644 --- a/tests/workbook/serialization_test_suite.hpp +++ b/tests/workbook/serialization_test_suite.hpp @@ -395,22 +395,27 @@ class serialization_test_suite : public test_suite wb.load(path_helper::test_file("13_custom_heights_widths.xlsx")); auto ws = wb.active_sheet(); - xlnt_assert_equals(ws.cell("A1").value(), "170xd"); - xlnt_assert_equals(ws.cell("B1").value(), "40xd"); - xlnt_assert_equals(ws.cell("C1").value(), "dxd"); - xlnt_assert_equals(ws.cell("A2").value(), "170x30"); - xlnt_assert_equals(ws.cell("B2").value(), "40x30"); - xlnt_assert_equals(ws.cell("C2").value(), "dx30"); - xlnt_assert_equals(ws.cell("A3").value(), "170x10"); - xlnt_assert_equals(ws.cell("B3").value(), "40x10"); - xlnt_assert_equals(ws.cell("C3").value(), "dx10"); - - xlnt_assert(!ws.row_properties(1).height.is_set()); - xlnt_assert_equals(ws.row_properties(2).height.get(), 30); - xlnt_assert_equals(ws.row_properties(3).height.get(), 10); - xlnt_assert_delta(ws.column_properties("A").width.get(), 27.617745535714285, 1.0E-9); - xlnt_assert_delta(ws.column_properties("B").width.get(), 5.9497767857142856, 1.0E-9); - xlnt_assert(!ws.column_properties("C").width.is_set()); + xlnt_assert_equals(ws.cell("A1").value(), "A1"); + xlnt_assert_equals(ws.cell("B1").value(), "B1"); + xlnt_assert_equals(ws.cell("D1").value(), "D1"); + xlnt_assert_equals(ws.cell("A2").value(), "A2"); + xlnt_assert_equals(ws.cell("B2").value(), "B2"); + xlnt_assert_equals(ws.cell("D2").value(), "D2"); + xlnt_assert_equals(ws.cell("A4").value(), "A4"); + xlnt_assert_equals(ws.cell("B4").value(), "B4"); + xlnt_assert_equals(ws.cell("D4").value(), "D4"); + + xlnt_assert_equals(ws.row_properties(1).height.get(), 100); + xlnt_assert(!ws.row_properties(2).height.is_set()); + xlnt_assert_equals(ws.row_properties(3).height.get(), 100); + xlnt_assert(!ws.row_properties(4).height.is_set()); + xlnt_assert_equals(ws.row_properties(5).height.get(), 100); + + xlnt_assert_delta(ws.column_properties("A").width.get(), 15.949776785714286, 1.0E-9); + xlnt_assert(!ws.column_properties("B").width.is_set()); + xlnt_assert_delta(ws.column_properties("C").width.get(), 15.949776785714286, 1.0E-9); + xlnt_assert(!ws.column_properties("D").width.is_set()); + xlnt_assert_delta(ws.column_properties("E").width.get(), 15.949776785714286, 1.0E-9); } void test_write_custom_heights_widths() @@ -418,21 +423,33 @@ class serialization_test_suite : public test_suite xlnt::workbook wb; auto ws = wb.active_sheet(); - ws.cell("A1").value("170xd"); - ws.cell("B1").value("40xd"); - ws.cell("C1").value("dxd"); - ws.cell("A2").value("170x30"); - ws.cell("B2").value("40x30"); - ws.cell("C2").value("dx30"); - ws.cell("A3").value("170x10"); - ws.cell("B3").value("40x10"); - ws.cell("C3").value("dx10"); - - ws.row_properties(2).height = 30; - ws.row_properties(3).height = 10; - - ws.column_properties("A").width = 27.617745535714285; - ws.column_properties("B").width = 5.9497767857142856; + ws.cell("A1").value("A1"); + ws.cell("B1").value("B1"); + ws.cell("D1").value("D1"); + ws.cell("A2").value("A2"); + ws.cell("B2").value("B2"); + ws.cell("D2").value("D2"); + ws.cell("A4").value("A4"); + ws.cell("B4").value("B4"); + ws.cell("D4").value("D4"); + + ws.row_properties(1).height = 100; + ws.row_properties(1).custom_height = true; + + ws.row_properties(3).height = 100; + ws.row_properties(3).custom_height = true; + + ws.row_properties(5).height = 100; + ws.row_properties(5).custom_height = true; + + ws.column_properties("A").width = 15.949776785714286; + ws.column_properties("A").custom_width = true; + + ws.column_properties("C").width = 15.949776785714286; + ws.column_properties("C").custom_width = true; + + ws.column_properties("E").width = 15.949776785714286; + ws.column_properties("E").custom_width = true; wb.save("temp.xlsx"); xlnt_assert(workbook_matches_file(wb, path_helper::test_file("13_custom_heights_widths.xlsx"))); diff --git a/tests/worksheet/worksheet_test_suite.hpp b/tests/worksheet/worksheet_test_suite.hpp index 5f308e141..be0b7c1b8 100644 --- a/tests/worksheet/worksheet_test_suite.hpp +++ b/tests/worksheet/worksheet_test_suite.hpp @@ -68,10 +68,14 @@ class worksheet_test_suite : public test_suite register_test(test_freeze_panes_horiz); register_test(test_freeze_panes_vert); register_test(test_freeze_panes_both); - register_test(test_min_column); - register_test(test_max_column); - register_test(test_min_row); - register_test(test_max_row); + register_test(test_lowest_column); + register_test(test_lowest_column_or_props); + register_test(test_highest_column); + register_test(test_highest_column_or_props); + register_test(test_lowest_row); + register_test(test_lowest_row_or_props); + register_test(test_highest_row); + register_test(test_highest_row_or_props); register_test(test_const_iterators); register_test(test_const_reverse_iterators); register_test(test_column_major_iterators); @@ -503,14 +507,22 @@ class worksheet_test_suite : public test_suite xlnt_assert_equals(view.pane().y_split, 3); } - void test_min_column() + void test_lowest_column() { xlnt::workbook wb; auto ws = wb.active_sheet(); xlnt_assert_equals(ws.lowest_column(), 1); } - void test_max_column() + void test_lowest_column_or_props() + { + xlnt::workbook wb; + auto ws = wb.active_sheet(); + ws.column_properties("J").width = 14.3; + xlnt_assert_equals(ws.lowest_column_or_props(), "J"); + } + + void test_highest_column() { xlnt::workbook wb; auto ws = wb.active_sheet(); @@ -518,17 +530,33 @@ class worksheet_test_suite : public test_suite ws[xlnt::cell_reference("F2")].value(32); ws[xlnt::cell_reference("F3")].formula("=F1+F2"); ws[xlnt::cell_reference("A4")].formula("=A1+A2+A3"); - xlnt_assert_equals(ws.highest_column(), 6); + xlnt_assert_equals(ws.highest_column(), "F"); + } + + void test_highest_column_or_props() + { + xlnt::workbook wb; + auto ws = wb.active_sheet(); + ws.column_properties("J").width = 14.3; + xlnt_assert_equals(ws.highest_column_or_props(), "J"); } - void test_min_row() + void test_lowest_row() { xlnt::workbook wb; auto ws = wb.active_sheet(); xlnt_assert_equals(ws.lowest_row(), 1); } - void test_max_row() + void test_lowest_row_or_props() + { + xlnt::workbook wb; + auto ws = wb.active_sheet(); + ws.row_properties(11).height = 14.3; + xlnt_assert_equals(ws.lowest_row_or_props(), 11); + } + + void test_highest_row() { xlnt::workbook wb; auto ws = wb.active_sheet(); @@ -536,6 +564,14 @@ class worksheet_test_suite : public test_suite xlnt_assert_equals(ws.highest_row(), 4); } + void test_highest_row_or_props() + { + xlnt::workbook wb; + auto ws = wb.active_sheet(); + ws.row_properties(11).height = 14.3; + xlnt_assert_equals(ws.highest_row_or_props(), 11); + } + void test_const_iterators() { xlnt::workbook wb;