From 7f44dc22740ea2543403f9244e59cd2f1d4697d7 Mon Sep 17 00:00:00 2001 From: Sewon Park Date: Thu, 21 Jan 2021 21:45:24 +0900 Subject: [PATCH] minimum support for xlsm --- .gitignore | 1 + include/xlnt/packaging/relationship.hpp | 1 + include/xlnt/workbook/workbook.hpp | 5 ++ .../detail/implementations/workbook_impl.hpp | 2 + .../serialization/custom_value_traits.cpp | 2 + .../serialization/custom_value_traits.hpp | 2 + source/detail/serialization/xlsx_consumer.cpp | 54 +++++++++++------ source/detail/serialization/xlsx_consumer.hpp | 5 ++ source/detail/serialization/xlsx_producer.cpp | 57 ++++++++++++++---- source/detail/serialization/xlsx_producer.hpp | 1 + source/workbook/workbook.cpp | 9 +++ tests/data/17_xlsm.xlsm | Bin 0 -> 16010 bytes tests/worksheet/worksheet_test_suite.cpp | 49 +++++++++++++++ 13 files changed, 157 insertions(+), 31 deletions(-) create mode 100644 tests/data/17_xlsm.xlsm diff --git a/.gitignore b/.gitignore index d4a279a9..9842ca68 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ python/record.txt python/xlntpyarrow.egg-info/ /x64/ .envrc +.vscode diff --git a/include/xlnt/packaging/relationship.hpp b/include/xlnt/packaging/relationship.hpp index 0fa50abf..df6cf526 100644 --- a/include/xlnt/packaging/relationship.hpp +++ b/include/xlnt/packaging/relationship.hpp @@ -88,6 +88,7 @@ enum class XLNT_API relationship_type vml_drawing, volatile_dependencies, worksheet, + vbaproject, // Worksheet parts hyperlink, diff --git a/include/xlnt/workbook/workbook.hpp b/include/xlnt/workbook/workbook.hpp index 0404e944..18da458f 100644 --- a/include/xlnt/workbook/workbook.hpp +++ b/include/xlnt/workbook/workbook.hpp @@ -798,6 +798,11 @@ public: /// const std::vector &thumbnail() const; + /// + /// Returns stored binary data. + /// + const std::unordered_map>& binaries() const; + // Calculation properties /// diff --git a/source/detail/implementations/workbook_impl.hpp b/source/detail/implementations/workbook_impl.hpp index 7c4e028f..bb8be229 100644 --- a/source/detail/implementations/workbook_impl.hpp +++ b/source/detail/implementations/workbook_impl.hpp @@ -102,6 +102,7 @@ struct workbook_impl && manifest_ == other.manifest_ && theme_ == other.theme_ && images_ == other.images_ + && binaries_ == other.binaries_ && core_properties_ == other.core_properties_ && extended_properties_ == other.extended_properties_ && custom_properties_ == other.custom_properties_ @@ -130,6 +131,7 @@ struct workbook_impl manifest manifest_; optional theme_; std::unordered_map> images_; + std::unordered_map> binaries_; std::vector> core_properties_; std::vector> extended_properties_; diff --git a/source/detail/serialization/custom_value_traits.cpp b/source/detail/serialization/custom_value_traits.cpp index dd8915ee..feb2f636 100644 --- a/source/detail/serialization/custom_value_traits.cpp +++ b/source/detail/serialization/custom_value_traits.cpp @@ -119,6 +119,8 @@ std::string to_string(relationship_type t) return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"; case relationship_type::volatile_dependencies: return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/volatileDependencies"; + case relationship_type::vbaproject: + return "http://schemas.microsoft.com/office/2006/relationships/vbaProject"; case relationship_type::image: return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"; case relationship_type::unknown: diff --git a/source/detail/serialization/custom_value_traits.hpp b/source/detail/serialization/custom_value_traits.hpp index ba477f29..041c86bf 100644 --- a/source/detail/serialization/custom_value_traits.hpp +++ b/source/detail/serialization/custom_value_traits.hpp @@ -162,6 +162,8 @@ relationship_type from_string(const std::string &string) return relationship_type::table_definition; else if (string == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/volatileDependencies") return relationship_type::volatile_dependencies; + else if (string == "http://schemas.microsoft.com/office/2006/relationships/vbaProject") + return relationship_type::vbaproject; else if (string == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image") return relationship_type::image; diff --git a/source/detail/serialization/xlsx_consumer.cpp b/source/detail/serialization/xlsx_consumer.cpp index ccabfb7e..a27eb985 100644 --- a/source/detail/serialization/xlsx_consumer.cpp +++ b/source/detail/serialization/xlsx_consumer.cpp @@ -1240,6 +1240,13 @@ worksheet xlsx_consumer::read_worksheet_end(const std::string &rel_id) read_drawings(ws, drawings_part); } + if (manifest.has_relationship(sheet_path, xlnt::relationship_type::printer_settings)) + { + read_part({workbook_rel, sheet_rel, + manifest.relationship(sheet_path, + relationship_type::printer_settings)}); + } + return ws; } @@ -1557,6 +1564,7 @@ void xlsx_consumer::read_part(const std::vector &rel_chain) break; case relationship_type::printer_settings: + read_binary(part_path); break; case relationship_type::custom_property: @@ -1589,6 +1597,10 @@ void xlsx_consumer::read_part(const std::vector &rel_chain) case relationship_type::table_definition: break; + case relationship_type::vbaproject: + read_binary(part_path); + break; + case relationship_type::image: read_image(part_path); break; @@ -1733,7 +1745,10 @@ void xlsx_consumer::read_office_document(const std::string &content_type) // CT_ "openxmlformats-officedocument.spreadsheetml.sheet.main+xml" && content_type != "application/vnd." - "openxmlformats-officedocument.spreadsheetml.template.main+xml") + "openxmlformats-officedocument.spreadsheetml.template.main+xml" + && content_type != + "application/vnd." + "ms-excel.sheet.macroEnabled.main+xml") { throw xlnt::invalid_file(content_type); } @@ -1995,25 +2010,20 @@ void xlsx_consumer::read_office_document(const std::string &content_type) // CT_ auto workbook_rel = manifest().relationship(path("/"), relationship_type::office_document); auto workbook_path = workbook_rel.target().path(); - if (manifest().has_relationship(workbook_path, relationship_type::shared_string_table)) - { - read_part({workbook_rel, - manifest().relationship(workbook_path, - relationship_type::shared_string_table)}); - } + const auto rel_types = { + relationship_type::shared_string_table, + relationship_type::stylesheet, + relationship_type::theme, + relationship_type::vbaproject, + }; - if (manifest().has_relationship(workbook_path, relationship_type::stylesheet)) + for (auto rel_type : rel_types) { - read_part({workbook_rel, - manifest().relationship(workbook_path, - relationship_type::stylesheet)}); - } - - if (manifest().has_relationship(workbook_path, relationship_type::theme)) - { - read_part({workbook_rel, - manifest().relationship(workbook_path, - relationship_type::theme)}); + if (manifest().has_relationship(workbook_path, rel_type)) + { + read_part({workbook_rel, + manifest().relationship(workbook_path, rel_type)}); + } } for (auto worksheet_rel : manifest().relationships(workbook_path, relationship_type::worksheet)) @@ -2907,6 +2917,14 @@ void xlsx_consumer::read_image(const xlnt::path &image_path) out_stream << image_streambuf.get(); } +void xlsx_consumer::read_binary(const xlnt::path &binary_path) +{ + auto binary_streambuf = archive_->open(binary_path); + vector_ostreambuf buffer(target_.d_->binaries_[binary_path.string()]); + std::ostream out_stream(&buffer); + out_stream << binary_streambuf.get(); +} + std::string xlsx_consumer::read_text() { auto text = std::string(); diff --git a/source/detail/serialization/xlsx_consumer.hpp b/source/detail/serialization/xlsx_consumer.hpp index 2dbafa01..b46ea973 100644 --- a/source/detail/serialization/xlsx_consumer.hpp +++ b/source/detail/serialization/xlsx_consumer.hpp @@ -252,6 +252,11 @@ private: /// void read_image(const path &part); + /// + /// + /// + void read_binary(const path &part); + // Common Section Readers /// diff --git a/source/detail/serialization/xlsx_producer.cpp b/source/detail/serialization/xlsx_producer.cpp index aacc72d0..6f83aadb 100644 --- a/source/detail/serialization/xlsx_producer.cpp +++ b/source/detail/serialization/xlsx_producer.cpp @@ -182,7 +182,10 @@ void xlsx_producer::begin_part(const path &part) end_part(); current_part_streambuf_ = archive_->open(part); current_part_stream_.rdbuf(current_part_streambuf_.get()); - current_part_serializer_.reset(new xml::serializer(current_part_stream_, part.string())); + + auto xml_serializer = new xml::serializer(current_part_stream_, part.string(), 0); + xml_serializer->xml_decl("1.0", "UTF-8", "yes"); + current_part_serializer_.reset(xml_serializer); } // Package Parts @@ -672,6 +675,16 @@ void xlsx_producer::write_workbook(const relationship &rel) if (child_rel.type() == relationship_type::calculation_chain) continue; path archive_path(child_rel.source().path().parent().append(child_rel.target().path())); + + // write binary + switch (child_rel.type()) + { + case relationship_type::vbaproject: + write_binary(archive_path); + continue; + } + + // write xml begin_part(archive_path); switch (child_rel.type()) @@ -766,6 +779,8 @@ void xlsx_producer::write_workbook(const relationship &rel) break; case relationship_type::table_definition: break; + case relationship_type::vbaproject: + break; case relationship_type::image: break; } @@ -3033,19 +3048,26 @@ void xlsx_producer::write_worksheet(const relationship &rel) archive_path = std::accumulate(split_part_path.begin(), split_part_path.end(), path(""), [](const path &a, const std::string &b) { return a.append(b); }); - begin_part(archive_path); + if (child_rel.type() == relationship_type::printer_settings) + { + write_binary(archive_path); + } + else + { + begin_part(archive_path); - if (child_rel.type() == relationship_type::comments) - { - write_comments(child_rel, ws, cells_with_comments); - } - else if (child_rel.type() == relationship_type::vml_drawing) - { - write_vml_drawings(child_rel, ws, cells_with_comments); - } - else if (child_rel.type() == relationship_type::drawings) - { - write_drawings(child_rel, ws); + if (child_rel.type() == relationship_type::comments) + { + write_comments(child_rel, ws, cells_with_comments); + } + else if (child_rel.type() == relationship_type::vml_drawing) + { + write_vml_drawings(child_rel, ws, cells_with_comments); + } + else if (child_rel.type() == relationship_type::drawings) + { + write_drawings(child_rel, ws); + } } } } @@ -3303,6 +3325,15 @@ void xlsx_producer::write_image(const path &image_path) std::ostream(image_streambuf.get()) << &buffer; } +void xlsx_producer::write_binary(const path &binary_path) +{ + end_part(); + + vector_istreambuf buffer(source_.d_->binaries_.at(binary_path.string())); + auto image_streambuf = archive_->open(binary_path); + std::ostream(image_streambuf.get()) << &buffer; +} + std::string xlsx_producer::write_bool(bool boolean) const { return boolean ? "1" : "0"; diff --git a/source/detail/serialization/xlsx_producer.hpp b/source/detail/serialization/xlsx_producer.hpp index 745b377b..dcfb2f15 100644 --- a/source/detail/serialization/xlsx_producer.hpp +++ b/source/detail/serialization/xlsx_producer.hpp @@ -99,6 +99,7 @@ private: void write_extended_properties(const relationship &rel); void write_custom_properties(const relationship &rel); void write_image(const path &image_path); + void write_binary(const path &binary_path); // SpreadsheetML-Specific Package Parts diff --git a/source/workbook/workbook.cpp b/source/workbook/workbook.cpp index ff96a72e..67e43edc 100644 --- a/source/workbook/workbook.cpp +++ b/source/workbook/workbook.cpp @@ -167,6 +167,8 @@ xlnt::path default_path(xlnt::relationship_type type, std::size_t index = 0) return path("/xl/vmlDrawing.xml"); case relationship_type::volatile_dependencies: return path("/xl/volatileDependencies.xml"); + case relationship_type::vbaproject: + return path("/xl/vbaProject.bin"); case relationship_type::worksheet: return path("/xl/worksheets/sheet" + std::to_string(index) + ".xml"); } @@ -246,6 +248,8 @@ std::string content_type(xlnt::relationship_type type) return "application/vnd.openxmlformats-officedocument.vmlDrawing"; case relationship_type::volatile_dependencies: return "application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml"; + case relationship_type::vbaproject: + return "application/vnd.ms-office.vbaProject"; case relationship_type::worksheet: return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"; } @@ -1439,6 +1443,11 @@ const std::vector &workbook::thumbnail() const return d_->images_.at(thumbnail_rel.target().to_string()); } +const std::unordered_map> &workbook::binaries() const +{ + return d_->binaries_; +} + style workbook::create_style(const std::string &name) { return d_->stylesheet_.get().create_style(name); diff --git a/tests/data/17_xlsm.xlsm b/tests/data/17_xlsm.xlsm new file mode 100644 index 0000000000000000000000000000000000000000..563ddc10b442a8279af60f6a4f93c3adaa761643 GIT binary patch literal 16010 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 literal 0 HcmV?d00001 diff --git a/tests/worksheet/worksheet_test_suite.cpp b/tests/worksheet/worksheet_test_suite.cpp index 64b47da1..31b0b00c 100644 --- a/tests/worksheet/worksheet_test_suite.cpp +++ b/tests/worksheet/worksheet_test_suite.cpp @@ -113,6 +113,7 @@ public: register_test(test_insert_too_many); register_test(test_insert_delete_moves_merges); register_test(test_hidden_sheet); + register_test(test_xlsm_read_write); } void test_new_worksheet() @@ -1590,5 +1591,53 @@ public: wb.load(path_helper::test_file("16_hidden_sheet.xlsx")); xlnt_assert_equals(wb.sheet_hidden_by_index(1), true); } + + void test_xlsm_read_write() + { + { + xlnt::workbook wb; + wb.load(path_helper::test_file("17_xlsm.xlsm")); + + auto ws = wb.sheet_by_title("Sheet1"); + auto rows = ws.rows(); + + xlnt_assert_equals(rows[0][0].value(), "Values"); + xlnt_assert_equals(rows[1][0].value(), 100); + xlnt_assert_equals(rows[2][0].value(), 200); + xlnt_assert_equals(rows[3][0].value(), 300); + xlnt_assert_equals(rows[4][0].value(), 400); + xlnt_assert_equals(rows[5][0].value(), 500); + + xlnt_assert_equals(rows[0][1].value(), "Sum"); + xlnt_assert_equals(rows[1][1].formula(), "SumVBA(A2:A6)"); + xlnt_assert_equals(rows[1][1].value(), 1500); + + // change sheet value + ws.cell("A6").value(1000); + + wb.save("17_xlsm_modified.xlsm"); + } + + { + xlnt::workbook wb; + wb.load("17_xlsm_modified.xlsm"); + + auto ws = wb.sheet_by_title("Sheet1"); + auto rows = ws.rows(); + + xlnt_assert_equals(rows[0][0].value(), "Values"); + xlnt_assert_equals(rows[1][0].value(), 100); + xlnt_assert_equals(rows[2][0].value(), 200); + xlnt_assert_equals(rows[3][0].value(), 300); + xlnt_assert_equals(rows[4][0].value(), 400); + // sheet value changed (500 -> 1000) + xlnt_assert_equals(rows[5][0].value(), 1000); + + xlnt_assert_equals(rows[0][1].value(), "Sum"); + xlnt_assert_equals(rows[1][1].formula(), "SumVBA(A2:A6)"); + // formula value not changed (we can't execute vba) + xlnt_assert_equals(rows[1][1].value(), 1500); + } + } }; static worksheet_test_suite x;