minimum support for xlsm

This commit is contained in:
Sewon Park 2021-01-21 21:45:24 +09:00
parent e66e417b0c
commit 7f44dc2274
13 changed files with 157 additions and 31 deletions

1
.gitignore vendored
View File

@ -18,3 +18,4 @@ python/record.txt
python/xlntpyarrow.egg-info/ python/xlntpyarrow.egg-info/
/x64/ /x64/
.envrc .envrc
.vscode

View File

@ -88,6 +88,7 @@ enum class XLNT_API relationship_type
vml_drawing, vml_drawing,
volatile_dependencies, volatile_dependencies,
worksheet, worksheet,
vbaproject,
// Worksheet parts // Worksheet parts
hyperlink, hyperlink,

View File

@ -798,6 +798,11 @@ public:
/// </summary> /// </summary>
const std::vector<std::uint8_t> &thumbnail() const; const std::vector<std::uint8_t> &thumbnail() const;
/// <summary>
/// Returns stored binary data.
/// </summary>
const std::unordered_map<std::string, std::vector<std::uint8_t>>& binaries() const;
// Calculation properties // Calculation properties
/// <summary> /// <summary>

View File

@ -102,6 +102,7 @@ struct workbook_impl
&& manifest_ == other.manifest_ && manifest_ == other.manifest_
&& theme_ == other.theme_ && theme_ == other.theme_
&& images_ == other.images_ && images_ == other.images_
&& binaries_ == other.binaries_
&& core_properties_ == other.core_properties_ && core_properties_ == other.core_properties_
&& extended_properties_ == other.extended_properties_ && extended_properties_ == other.extended_properties_
&& custom_properties_ == other.custom_properties_ && custom_properties_ == other.custom_properties_
@ -130,6 +131,7 @@ struct workbook_impl
manifest manifest_; manifest manifest_;
optional<theme> theme_; optional<theme> theme_;
std::unordered_map<std::string, std::vector<std::uint8_t>> images_; std::unordered_map<std::string, std::vector<std::uint8_t>> images_;
std::unordered_map<std::string, std::vector<std::uint8_t>> binaries_;
std::vector<std::pair<xlnt::core_property, variant>> core_properties_; std::vector<std::pair<xlnt::core_property, variant>> core_properties_;
std::vector<std::pair<xlnt::extended_property, variant>> extended_properties_; std::vector<std::pair<xlnt::extended_property, variant>> extended_properties_;

View File

@ -119,6 +119,8 @@ std::string to_string(relationship_type t)
return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"; return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table";
case relationship_type::volatile_dependencies: case relationship_type::volatile_dependencies:
return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/volatileDependencies"; 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: case relationship_type::image:
return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"; return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
case relationship_type::unknown: case relationship_type::unknown:

View File

@ -162,6 +162,8 @@ relationship_type from_string(const std::string &string)
return relationship_type::table_definition; return relationship_type::table_definition;
else if (string == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/volatileDependencies") else if (string == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/volatileDependencies")
return relationship_type::volatile_dependencies; 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") else if (string == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image")
return relationship_type::image; return relationship_type::image;

View File

@ -1240,6 +1240,13 @@ worksheet xlsx_consumer::read_worksheet_end(const std::string &rel_id)
read_drawings(ws, drawings_part); 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; return ws;
} }
@ -1557,6 +1564,7 @@ void xlsx_consumer::read_part(const std::vector<relationship> &rel_chain)
break; break;
case relationship_type::printer_settings: case relationship_type::printer_settings:
read_binary(part_path);
break; break;
case relationship_type::custom_property: case relationship_type::custom_property:
@ -1589,6 +1597,10 @@ void xlsx_consumer::read_part(const std::vector<relationship> &rel_chain)
case relationship_type::table_definition: case relationship_type::table_definition:
break; break;
case relationship_type::vbaproject:
read_binary(part_path);
break;
case relationship_type::image: case relationship_type::image:
read_image(part_path); read_image(part_path);
break; break;
@ -1733,7 +1745,10 @@ void xlsx_consumer::read_office_document(const std::string &content_type) // CT_
"openxmlformats-officedocument.spreadsheetml.sheet.main+xml" "openxmlformats-officedocument.spreadsheetml.sheet.main+xml"
&& content_type != && content_type !=
"application/vnd." "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); 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_rel = manifest().relationship(path("/"), relationship_type::office_document);
auto workbook_path = workbook_rel.target().path(); auto workbook_path = workbook_rel.target().path();
if (manifest().has_relationship(workbook_path, relationship_type::shared_string_table)) const auto rel_types = {
{ relationship_type::shared_string_table,
read_part({workbook_rel, relationship_type::stylesheet,
manifest().relationship(workbook_path, relationship_type::theme,
relationship_type::shared_string_table)}); relationship_type::vbaproject,
} };
if (manifest().has_relationship(workbook_path, relationship_type::stylesheet)) for (auto rel_type : rel_types)
{ {
read_part({workbook_rel, if (manifest().has_relationship(workbook_path, rel_type))
manifest().relationship(workbook_path, {
relationship_type::stylesheet)}); read_part({workbook_rel,
} manifest().relationship(workbook_path, rel_type)});
}
if (manifest().has_relationship(workbook_path, relationship_type::theme))
{
read_part({workbook_rel,
manifest().relationship(workbook_path,
relationship_type::theme)});
} }
for (auto worksheet_rel : manifest().relationships(workbook_path, relationship_type::worksheet)) 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(); 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() std::string xlsx_consumer::read_text()
{ {
auto text = std::string(); auto text = std::string();

View File

@ -252,6 +252,11 @@ private:
/// </summary> /// </summary>
void read_image(const path &part); void read_image(const path &part);
/// <summary>
///
/// </summary>
void read_binary(const path &part);
// Common Section Readers // Common Section Readers
/// <summary> /// <summary>

View File

@ -182,7 +182,10 @@ void xlsx_producer::begin_part(const path &part)
end_part(); end_part();
current_part_streambuf_ = archive_->open(part); current_part_streambuf_ = archive_->open(part);
current_part_stream_.rdbuf(current_part_streambuf_.get()); 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 // Package Parts
@ -672,6 +675,16 @@ void xlsx_producer::write_workbook(const relationship &rel)
if (child_rel.type() == relationship_type::calculation_chain) continue; if (child_rel.type() == relationship_type::calculation_chain) continue;
path archive_path(child_rel.source().path().parent().append(child_rel.target().path())); 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); begin_part(archive_path);
switch (child_rel.type()) switch (child_rel.type())
@ -766,6 +779,8 @@ void xlsx_producer::write_workbook(const relationship &rel)
break; break;
case relationship_type::table_definition: case relationship_type::table_definition:
break; break;
case relationship_type::vbaproject:
break;
case relationship_type::image: case relationship_type::image:
break; 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(""), archive_path = std::accumulate(split_part_path.begin(), split_part_path.end(), path(""),
[](const path &a, const std::string &b) { return a.append(b); }); [](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) if (child_rel.type() == relationship_type::comments)
{ {
write_comments(child_rel, ws, cells_with_comments); write_comments(child_rel, ws, cells_with_comments);
} }
else if (child_rel.type() == relationship_type::vml_drawing) else if (child_rel.type() == relationship_type::vml_drawing)
{ {
write_vml_drawings(child_rel, ws, cells_with_comments); write_vml_drawings(child_rel, ws, cells_with_comments);
} }
else if (child_rel.type() == relationship_type::drawings) else if (child_rel.type() == relationship_type::drawings)
{ {
write_drawings(child_rel, ws); write_drawings(child_rel, ws);
}
} }
} }
} }
@ -3303,6 +3325,15 @@ void xlsx_producer::write_image(const path &image_path)
std::ostream(image_streambuf.get()) << &buffer; 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 std::string xlsx_producer::write_bool(bool boolean) const
{ {
return boolean ? "1" : "0"; return boolean ? "1" : "0";

View File

@ -99,6 +99,7 @@ private:
void write_extended_properties(const relationship &rel); void write_extended_properties(const relationship &rel);
void write_custom_properties(const relationship &rel); void write_custom_properties(const relationship &rel);
void write_image(const path &image_path); void write_image(const path &image_path);
void write_binary(const path &binary_path);
// SpreadsheetML-Specific Package Parts // SpreadsheetML-Specific Package Parts

View File

@ -167,6 +167,8 @@ xlnt::path default_path(xlnt::relationship_type type, std::size_t index = 0)
return path("/xl/vmlDrawing.xml"); return path("/xl/vmlDrawing.xml");
case relationship_type::volatile_dependencies: case relationship_type::volatile_dependencies:
return path("/xl/volatileDependencies.xml"); return path("/xl/volatileDependencies.xml");
case relationship_type::vbaproject:
return path("/xl/vbaProject.bin");
case relationship_type::worksheet: case relationship_type::worksheet:
return path("/xl/worksheets/sheet" + std::to_string(index) + ".xml"); 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"; return "application/vnd.openxmlformats-officedocument.vmlDrawing";
case relationship_type::volatile_dependencies: case relationship_type::volatile_dependencies:
return "application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml"; return "application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml";
case relationship_type::vbaproject:
return "application/vnd.ms-office.vbaProject";
case relationship_type::worksheet: case relationship_type::worksheet:
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"; return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
} }
@ -1439,6 +1443,11 @@ const std::vector<std::uint8_t> &workbook::thumbnail() const
return d_->images_.at(thumbnail_rel.target().to_string()); return d_->images_.at(thumbnail_rel.target().to_string());
} }
const std::unordered_map<std::string, std::vector<std::uint8_t>> &workbook::binaries() const
{
return d_->binaries_;
}
style workbook::create_style(const std::string &name) style workbook::create_style(const std::string &name)
{ {
return d_->stylesheet_.get().create_style(name); return d_->stylesheet_.get().create_style(name);

BIN
tests/data/17_xlsm.xlsm Normal file

Binary file not shown.

View File

@ -113,6 +113,7 @@ public:
register_test(test_insert_too_many); register_test(test_insert_too_many);
register_test(test_insert_delete_moves_merges); register_test(test_insert_delete_moves_merges);
register_test(test_hidden_sheet); register_test(test_hidden_sheet);
register_test(test_xlsm_read_write);
} }
void test_new_worksheet() void test_new_worksheet()
@ -1590,5 +1591,53 @@ public:
wb.load(path_helper::test_file("16_hidden_sheet.xlsx")); wb.load(path_helper::test_file("16_hidden_sheet.xlsx"));
xlnt_assert_equals(wb.sheet_hidden_by_index(1), true); 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<std::string>(), "Values");
xlnt_assert_equals(rows[1][0].value<int>(), 100);
xlnt_assert_equals(rows[2][0].value<int>(), 200);
xlnt_assert_equals(rows[3][0].value<int>(), 300);
xlnt_assert_equals(rows[4][0].value<int>(), 400);
xlnt_assert_equals(rows[5][0].value<int>(), 500);
xlnt_assert_equals(rows[0][1].value<std::string>(), "Sum");
xlnt_assert_equals(rows[1][1].formula(), "SumVBA(A2:A6)");
xlnt_assert_equals(rows[1][1].value<int>(), 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<std::string>(), "Values");
xlnt_assert_equals(rows[1][0].value<int>(), 100);
xlnt_assert_equals(rows[2][0].value<int>(), 200);
xlnt_assert_equals(rows[3][0].value<int>(), 300);
xlnt_assert_equals(rows[4][0].value<int>(), 400);
// sheet value changed (500 -> 1000)
xlnt_assert_equals(rows[5][0].value<int>(), 1000);
xlnt_assert_equals(rows[0][1].value<std::string>(), "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<int>(), 1500);
}
}
}; };
static worksheet_test_suite x; static worksheet_test_suite x;