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 00000000..563ddc10 Binary files /dev/null and b/tests/data/17_xlsm.xlsm differ 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;