mirror of
https://github.com/tfussell/xlnt.git
synced 2024-03-22 13:11:17 +08:00
Merge pull request #647 from tfussell/shared-array-formulas
Implement shared and array formulas
This commit is contained in:
commit
1945691bb6
|
@ -115,6 +115,11 @@ public:
|
|||
/// </summary>
|
||||
std::string to_string() const;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given cell reference is within the bounds of this range reference.
|
||||
/// </summary>
|
||||
bool contains(const cell_reference &ref) const;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this range is equivalent to the other range.
|
||||
/// </summary>
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
#include <xlnt/worksheet/range_reference.hpp>
|
||||
#include <xlnt/worksheet/row_properties.hpp>
|
||||
#include <xlnt/worksheet/selection.hpp>
|
||||
#include <xlnt/worksheet/sheet_format_properties.hpp>
|
||||
#include <xlnt/worksheet/sheet_protection.hpp>
|
||||
#include <xlnt/worksheet/sheet_view.hpp>
|
||||
#include <xlnt/worksheet/worksheet.hpp>
|
||||
|
|
|
@ -165,7 +165,7 @@ xlnt::cell_type type_from_string(const std::string &str)
|
|||
return xlnt::cell::type::shared_string;
|
||||
}
|
||||
|
||||
xlnt::detail::Cell parse_cell(xlnt::row_t row_arg, xml::parser *parser)
|
||||
xlnt::detail::Cell parse_cell(xlnt::row_t row_arg, xml::parser *parser, std::unordered_map<std::string, std::string> &array_formulae, std::unordered_map<int, std::string> &shared_formulae)
|
||||
{
|
||||
xlnt::detail::Cell c;
|
||||
for (auto &attr : parser->attribute_map())
|
||||
|
@ -202,6 +202,16 @@ xlnt::detail::Cell parse_cell(xlnt::row_t row_arg, xml::parser *parser)
|
|||
switch (e)
|
||||
{
|
||||
case xml::parser::start_element: {
|
||||
if (string_equal(parser->name(), "f") && parser->attribute_present("t"))
|
||||
{
|
||||
// Skip shared formulas with a ref attribute because it indicates that this
|
||||
// is the master cell which will be handled in the xml::parser::characters case.
|
||||
if (parser->attribute("t") == "shared" && !parser->attribute_present("ref"))
|
||||
{
|
||||
auto shared_index = parser->attribute<int>("si");
|
||||
c.formula_string = shared_formulae[shared_index];
|
||||
}
|
||||
}
|
||||
++level;
|
||||
break;
|
||||
}
|
||||
|
@ -223,6 +233,21 @@ xlnt::detail::Cell parse_cell(xlnt::row_t row_arg, xml::parser *parser)
|
|||
else if (string_equal(parser->name(), "f"))
|
||||
{
|
||||
c.formula_string += std::move(parser->value());
|
||||
|
||||
if (parser->attribute_present("t"))
|
||||
{
|
||||
auto formula_ref = parser->attribute("ref");
|
||||
auto formula_type = parser->attribute("t");
|
||||
if (formula_type == "shared")
|
||||
{
|
||||
auto shared_index = parser->attribute<int>("si");
|
||||
shared_formulae[shared_index] = c.formula_string;
|
||||
}
|
||||
else if (formula_type == "array")
|
||||
{
|
||||
array_formulae[formula_ref] = c.formula_string;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (level == 3)
|
||||
|
@ -251,7 +276,7 @@ xlnt::detail::Cell parse_cell(xlnt::row_t row_arg, xml::parser *parser)
|
|||
}
|
||||
|
||||
// <row> inside <sheetData> element
|
||||
std::pair<xlnt::row_properties, int> parse_row(xml::parser *parser, xlnt::detail::number_serialiser &converter, std::vector<xlnt::detail::Cell> &parsed_cells)
|
||||
std::pair<xlnt::row_properties, int> parse_row(xml::parser *parser, xlnt::detail::number_serialiser &converter, std::vector<xlnt::detail::Cell> &parsed_cells, std::unordered_map<std::string, std::string> &array_formulae, std::unordered_map<int, std::string> &shared_formulae)
|
||||
{
|
||||
std::pair<xlnt::row_properties, int> props;
|
||||
for (auto &attr : parser->attribute_map())
|
||||
|
@ -301,7 +326,7 @@ std::pair<xlnt::row_properties, int> parse_row(xml::parser *parser, xlnt::detail
|
|||
switch (e)
|
||||
{
|
||||
case xml::parser::start_element: {
|
||||
parsed_cells.push_back(parse_cell(static_cast<xlnt::row_t>(props.second), parser));
|
||||
parsed_cells.push_back(parse_cell(static_cast<xlnt::row_t>(props.second), parser, array_formulae, shared_formulae));
|
||||
break;
|
||||
}
|
||||
case xml::parser::end_element: {
|
||||
|
@ -326,7 +351,7 @@ std::pair<xlnt::row_properties, int> parse_row(xml::parser *parser, xlnt::detail
|
|||
}
|
||||
|
||||
// <sheetData> inside <worksheet> element
|
||||
Sheet_Data parse_sheet_data(xml::parser *parser, xlnt::detail::number_serialiser &converter)
|
||||
Sheet_Data parse_sheet_data(xml::parser *parser, xlnt::detail::number_serialiser &converter, std::unordered_map<std::string, std::string> &array_formulae, std::unordered_map<int, std::string> &shared_formulae)
|
||||
{
|
||||
Sheet_Data sheet_data;
|
||||
int level = 1; // nesting level
|
||||
|
@ -339,7 +364,7 @@ Sheet_Data parse_sheet_data(xml::parser *parser, xlnt::detail::number_serialiser
|
|||
switch (e)
|
||||
{
|
||||
case xml::parser::start_element: {
|
||||
sheet_data.parsed_rows.push_back(parse_row(parser, converter, sheet_data.parsed_cells));
|
||||
sheet_data.parsed_rows.push_back(parse_row(parser, converter, sheet_data.parsed_cells, array_formulae, shared_formulae));
|
||||
break;
|
||||
}
|
||||
case xml::parser::end_element: {
|
||||
|
@ -429,6 +454,9 @@ std::string xlsx_consumer::read_worksheet_begin(const std::string &rel_id)
|
|||
{
|
||||
streaming_cell_.reset(new detail::cell_impl());
|
||||
}
|
||||
|
||||
array_formulae_.clear();
|
||||
shared_formulae_.clear();
|
||||
|
||||
auto title = std::find_if(target_.d_->sheet_title_rel_id_map_.begin(),
|
||||
target_.d_->sheet_title_rel_id_map_.end(),
|
||||
|
@ -742,7 +770,8 @@ void xlsx_consumer::read_worksheet_sheetdata()
|
|||
{
|
||||
return;
|
||||
}
|
||||
Sheet_Data ws_data = parse_sheet_data(parser_, converter_);
|
||||
|
||||
auto ws_data = parse_sheet_data(parser_, converter_, array_formulae_, shared_formulae_);
|
||||
// NOTE: parse->construct are seperated here and could easily be threaded
|
||||
// with a SPSC queue for what is likely to be an easy performance win
|
||||
for (auto &row : ws_data.parsed_rows)
|
||||
|
@ -803,6 +832,8 @@ void xlsx_consumer::read_worksheet_sheetdata()
|
|||
}
|
||||
}
|
||||
stack_.pop_back();
|
||||
|
||||
|
||||
}
|
||||
|
||||
worksheet xlsx_consumer::read_worksheet_end(const std::string &rel_id)
|
||||
|
@ -1258,6 +1289,17 @@ worksheet xlsx_consumer::read_worksheet_end(const std::string &rel_id)
|
|||
manifest.relationship(sheet_path,
|
||||
relationship_type::printer_settings)});
|
||||
}
|
||||
|
||||
for (auto array_formula : array_formulae_)
|
||||
{
|
||||
for (auto row : ws.range(array_formula.first))
|
||||
{
|
||||
for (auto cell : row)
|
||||
{
|
||||
cell.formula(array_formula.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
@ -1356,10 +1398,7 @@ bool xlsx_consumer::has_cell()
|
|||
|
||||
auto has_value = false;
|
||||
auto value_string = std::string();
|
||||
|
||||
auto has_formula = false;
|
||||
auto has_shared_formula = false;
|
||||
auto formula_value_string = std::string();
|
||||
auto formula_string = std::string();
|
||||
|
||||
while (in_element(qn("spreadsheetml", "c")))
|
||||
{
|
||||
|
@ -1372,17 +1411,56 @@ bool xlsx_consumer::has_cell()
|
|||
}
|
||||
else if (current_element == qn("spreadsheetml", "f")) // CT_CellFormula
|
||||
{
|
||||
has_formula = true;
|
||||
auto has_shared_formula = false;
|
||||
auto has_array_formula = false;
|
||||
auto is_master_cell = false;
|
||||
auto shared_formula_index = 0;
|
||||
auto formula_range = range_reference();
|
||||
|
||||
if (parser().attribute_present("t"))
|
||||
{
|
||||
has_shared_formula = parser().attribute("t") == "shared";
|
||||
auto formula_type = parser().attribute("t");
|
||||
if (formula_type == "shared")
|
||||
{
|
||||
has_shared_formula = true;
|
||||
shared_formula_index = parser().attribute<int>("si");
|
||||
if (parser().attribute_present("ref"))
|
||||
{
|
||||
is_master_cell = true;
|
||||
}
|
||||
}
|
||||
else if (formula_type == "array")
|
||||
{
|
||||
has_array_formula = true;
|
||||
formula_range = range_reference(parser().attribute("ref"));
|
||||
is_master_cell = true;
|
||||
}
|
||||
}
|
||||
|
||||
skip_attributes({"aca", "ref", "dt2D", "dtr", "del1",
|
||||
"del2", "r1", "r2", "ca", "si", "bx"});
|
||||
skip_attributes({"aca", "dt2D", "dtr", "del1", "del2", "r1",
|
||||
"r2", "ca", "bx"});
|
||||
|
||||
formula_value_string = read_text();
|
||||
formula_string = read_text();
|
||||
|
||||
if (is_master_cell)
|
||||
{
|
||||
if (has_shared_formula)
|
||||
{
|
||||
shared_formulae_[shared_formula_index] = formula_string;
|
||||
}
|
||||
else if (has_array_formula)
|
||||
{
|
||||
array_formulae_[formula_range.to_string()] = formula_string;
|
||||
}
|
||||
}
|
||||
else if (has_shared_formula)
|
||||
{
|
||||
auto shared_formula = shared_formulae_.find(shared_formula_index);
|
||||
if (shared_formula != shared_formulae_.end())
|
||||
{
|
||||
formula_string = shared_formula->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (current_element == qn("spreadsheetml", "is")) // CT_Rst
|
||||
{
|
||||
|
@ -1401,9 +1479,9 @@ bool xlsx_consumer::has_cell()
|
|||
|
||||
expect_end_element(qn("spreadsheetml", "c"));
|
||||
|
||||
if (has_formula && !has_shared_formula)
|
||||
if (!formula_string.empty())
|
||||
{
|
||||
cell.formula(formula_value_string);
|
||||
cell.formula(formula_string);
|
||||
}
|
||||
|
||||
if (has_value)
|
||||
|
|
|
@ -45,6 +45,7 @@ class manifest;
|
|||
template<typename T>
|
||||
class optional;
|
||||
class path;
|
||||
class range_reference;
|
||||
class relationship;
|
||||
class streaming_workbook_reader;
|
||||
class variant;
|
||||
|
@ -417,6 +418,9 @@ private:
|
|||
bool streaming_ = false;
|
||||
|
||||
std::unique_ptr<detail::cell_impl> streaming_cell_;
|
||||
|
||||
std::unordered_map<int, std::string> shared_formulae_;
|
||||
std::unordered_map<std::string, std::string> array_formulae_;
|
||||
|
||||
detail::worksheet_impl *current_worksheet_;
|
||||
number_serialiser converter_;
|
||||
|
|
|
@ -132,12 +132,9 @@ const cell_vector range::vector(std::size_t vector_index) const
|
|||
return cell_vector(ws_, cursor, ref_, order_, skip_null_, false);
|
||||
}
|
||||
|
||||
bool range::contains(const cell_reference &ref)
|
||||
bool range::contains(const cell_reference &cell_ref)
|
||||
{
|
||||
return ref_.top_left().column_index() <= ref.column_index()
|
||||
&& ref_.bottom_right().column_index() >= ref.column_index()
|
||||
&& ref_.top_left().row() <= ref.row()
|
||||
&& ref_.bottom_right().row() >= ref.row();
|
||||
return ref_.contains(cell_ref);
|
||||
}
|
||||
|
||||
range range::alignment(const xlnt::alignment &new_alignment)
|
||||
|
|
|
@ -145,6 +145,14 @@ cell_reference range_reference::bottom_right() const
|
|||
return bottom_right_;
|
||||
}
|
||||
|
||||
bool range_reference::contains(const cell_reference &ref) const
|
||||
{
|
||||
return top_left_.column_index() <= ref.column_index()
|
||||
&& bottom_right_.column_index() >= ref.column_index()
|
||||
&& top_left_.row() <= ref.row()
|
||||
&& bottom_right_.row() >= ref.row();
|
||||
}
|
||||
|
||||
bool range_reference::operator==(const std::string &reference_string) const
|
||||
{
|
||||
return *this == range_reference(reference_string);
|
||||
|
|
BIN
tests/data/18_formulae.xlsx
Normal file
BIN
tests/data/18_formulae.xlsx
Normal file
Binary file not shown.
|
@ -23,31 +23,7 @@
|
|||
|
||||
#include <iostream>
|
||||
|
||||
#include <xlnt/cell/cell.hpp>
|
||||
#include <xlnt/cell/comment.hpp>
|
||||
#include <xlnt/cell/hyperlink.hpp>
|
||||
#include <xlnt/styles/border.hpp>
|
||||
#include <xlnt/styles/fill.hpp>
|
||||
#include <xlnt/styles/font.hpp>
|
||||
#include <xlnt/styles/format.hpp>
|
||||
#include <xlnt/styles/number_format.hpp>
|
||||
#include <xlnt/styles/style.hpp>
|
||||
#include <xlnt/utils/date.hpp>
|
||||
#include <xlnt/utils/datetime.hpp>
|
||||
#include <xlnt/utils/time.hpp>
|
||||
#include <xlnt/utils/timedelta.hpp>
|
||||
#include <xlnt/utils/variant.hpp>
|
||||
#include <xlnt/workbook/metadata_property.hpp>
|
||||
#include <xlnt/workbook/streaming_workbook_reader.hpp>
|
||||
#include <xlnt/workbook/streaming_workbook_writer.hpp>
|
||||
#include <xlnt/workbook/workbook.hpp>
|
||||
#include <xlnt/worksheet/column_properties.hpp>
|
||||
#include <xlnt/worksheet/header_footer.hpp>
|
||||
#include <xlnt/worksheet/row_properties.hpp>
|
||||
#include <xlnt/worksheet/sheet_format_properties.hpp>
|
||||
#include <xlnt/worksheet/worksheet.hpp>
|
||||
#include <detail/cryptography/xlsx_crypto_consumer.hpp>
|
||||
#include <detail/serialization/vector_streambuf.hpp>
|
||||
#include <xlnt/xlnt.hpp>
|
||||
#include <helpers/path_helper.hpp>
|
||||
#include <helpers/temporary_file.hpp>
|
||||
#include <helpers/test_suite.hpp>
|
||||
|
@ -62,7 +38,7 @@ public:
|
|||
register_test(test_produce_simple_excel);
|
||||
register_test(test_save_after_sheet_deletion);
|
||||
register_test(test_write_comments_hyperlinks_formulae);
|
||||
register_test(test_save_after_clear_all_formulae);
|
||||
register_test(test_save_after_clear_formula);
|
||||
register_test(test_load_non_xlsx);
|
||||
register_test(test_decrypt_agile);
|
||||
register_test(test_decrypt_libre_office);
|
||||
|
@ -304,21 +280,20 @@ public:
|
|||
xlnt_assert(workbook_matches_file(wb, path));
|
||||
}
|
||||
|
||||
void test_save_after_clear_all_formulae()
|
||||
void test_save_after_clear_formula()
|
||||
{
|
||||
xlnt::workbook wb;
|
||||
const auto path = path_helper::test_file("10_comments_hyperlinks_formulae.xlsx");
|
||||
const auto path = path_helper::test_file("18_formulae.xlsx");
|
||||
wb.load(path);
|
||||
|
||||
auto ws1 = wb.sheet_by_index(0);
|
||||
xlnt_assert(ws1.cell("C1").has_formula());
|
||||
xlnt_assert_equals(ws1.cell("C1").formula(), "CONCATENATE(C2,C3)");
|
||||
ws1.cell("C1").clear_formula();
|
||||
|
||||
auto ws2 = wb.sheet_by_index(1);
|
||||
xlnt_assert(ws2.cell("C1").has_formula());
|
||||
xlnt_assert_equals(ws2.cell("C1").formula(), "C2*C3");
|
||||
ws2.cell("C1").clear_formula();
|
||||
for (auto row : ws1)
|
||||
{
|
||||
for (auto cell : row)
|
||||
{
|
||||
cell.clear_formula();
|
||||
}
|
||||
}
|
||||
|
||||
wb.save("clear_formulae.xlsx");
|
||||
}
|
||||
|
@ -425,20 +400,41 @@ public:
|
|||
void test_read_formulae()
|
||||
{
|
||||
xlnt::workbook wb;
|
||||
const auto path = path_helper::test_file("10_comments_hyperlinks_formulae.xlsx");
|
||||
const auto path = path_helper::test_file("18_formulae.xlsx");
|
||||
wb.load(path);
|
||||
|
||||
auto ws1 = wb.sheet_by_index(0);
|
||||
xlnt_assert(ws1.cell("C1").has_formula());
|
||||
xlnt_assert_equals(ws1.cell("C1").formula(), "CONCATENATE(C2,C3)");
|
||||
xlnt_assert_equals(ws1.cell("C2").value<std::string>(), "a");
|
||||
xlnt_assert_equals(ws1.cell("C3").value<std::string>(), "b");
|
||||
|
||||
// test has_formula
|
||||
// A1:B3 are plain text cells
|
||||
// C1:G3,I2,F4 have formulae
|
||||
for (auto row = 1; row < 4; row++)
|
||||
{
|
||||
for (auto column = 1; column < 8; column++)
|
||||
{
|
||||
if (column < 3)
|
||||
{
|
||||
xlnt_assert(!ws1.cell(column, row).has_formula());
|
||||
}
|
||||
else
|
||||
{
|
||||
xlnt_assert(ws1.cell(column, row).has_formula());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto ws2 = wb.sheet_by_index(1);
|
||||
xlnt_assert(ws2.cell("C1").has_formula());
|
||||
xlnt_assert_equals(ws2.cell("C1").formula(), "C2*C3");
|
||||
xlnt_assert_equals(ws2.cell("C2").value<int>(), 2);
|
||||
xlnt_assert_equals(ws2.cell("C3").value<int>(), 3);
|
||||
xlnt_assert(ws1.cell("I2").has_formula());
|
||||
xlnt_assert(ws1.cell("F4").has_formula());
|
||||
|
||||
xlnt_assert(!ws1.cell("C9").has_formula()); // empty cell
|
||||
xlnt_assert(!ws1.cell("F5").has_formula()); // text cell
|
||||
|
||||
xlnt_assert_equals(ws1.cell("C1").formula(), "B1^2"); // basic math with reference
|
||||
xlnt_assert_equals(ws1.cell("D1").formula(), "CONCATENATE(A1,B1)"); // concat with ref
|
||||
xlnt_assert_equals(ws1.cell("E1").formula(), "CONCATENATE($C$1,$D$1)"); // concat with absolute ref
|
||||
xlnt_assert_equals(ws1.cell("F1").formula(), "1+1"); // basic math
|
||||
xlnt_assert_equals(ws1.cell("G1").formula(), "PI()"); // constant
|
||||
xlnt_assert_equals(ws1.cell("I2").formula(), "COS(C2)+IMAGINARY(SIN(B2))"); // fancy math
|
||||
}
|
||||
|
||||
void test_read_headers_and_footers()
|
||||
|
@ -657,7 +653,7 @@ public:
|
|||
{
|
||||
xlnt_assert(round_trip_matches_rw(path_helper::test_file("13_custom_heights_widths.xlsx")));
|
||||
}
|
||||
|
||||
|
||||
void test_round_trip_rw_encrypted_agile()
|
||||
{
|
||||
xlnt_assert(round_trip_matches_rw(path_helper::test_file("5_encrypted_agile.xlsx"), "secret"));
|
||||
|
|
Loading…
Reference in New Issue
Block a user