mirror of
https://github.com/tfussell/xlnt.git
synced 2024-03-22 13:11:17 +08:00
minimum support for xlsm
This commit is contained in:
parent
e66e417b0c
commit
7f44dc2274
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -18,3 +18,4 @@ python/record.txt
|
||||||
python/xlntpyarrow.egg-info/
|
python/xlntpyarrow.egg-info/
|
||||||
/x64/
|
/x64/
|
||||||
.envrc
|
.envrc
|
||||||
|
.vscode
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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_;
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
{
|
||||||
|
if (manifest().has_relationship(workbook_path, rel_type))
|
||||||
{
|
{
|
||||||
read_part({workbook_rel,
|
read_part({workbook_rel,
|
||||||
manifest().relationship(workbook_path,
|
manifest().relationship(workbook_path, rel_type)});
|
||||||
relationship_type::stylesheet)});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,6 +3048,12 @@ 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); });
|
||||||
|
|
||||||
|
if (child_rel.type() == relationship_type::printer_settings)
|
||||||
|
{
|
||||||
|
write_binary(archive_path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
begin_part(archive_path);
|
begin_part(archive_path);
|
||||||
|
|
||||||
if (child_rel.type() == relationship_type::comments)
|
if (child_rel.type() == relationship_type::comments)
|
||||||
|
@ -3050,6 +3071,7 @@ void xlsx_producer::write_worksheet(const relationship &rel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Sheet Relationship Target Parts
|
// Sheet Relationship Target Parts
|
||||||
|
|
||||||
|
@ -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";
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
BIN
tests/data/17_xlsm.xlsm
Normal file
Binary file not shown.
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user