mirror of
https://github.com/tfussell/xlnt.git
synced 2024-03-22 13:11:17 +08:00
separate crypto_helper into header file, fix lots of stuff
This commit is contained in:
parent
84011553a8
commit
767d498dac
|
@ -183,9 +183,20 @@ public:
|
||||||
std::string hyperlink() const;
|
std::string hyperlink() const;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a hyperlink to this cell pointing to the URI of the given value.
|
/// Adds a hyperlink to this cell pointing to the URL of the given value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void hyperlink(const std::string &value);
|
void hyperlink(const std::string &url);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a hyperlink to this cell pointing to the URI of the given value and sets
|
||||||
|
/// the text value of the cell to the given parameter.
|
||||||
|
/// </summary>
|
||||||
|
void hyperlink(const std::string &url, const std::string &display);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an internal hyperlink to this cell pointing to the given cell.
|
||||||
|
/// </summary>
|
||||||
|
void hyperlink(xlnt::cell target);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if this cell has a hyperlink set.
|
/// Returns true if this cell has a hyperlink set.
|
||||||
|
|
|
@ -450,20 +450,20 @@ public:
|
||||||
/// Serializes the workbook into an XLSX file encrypted with the given password
|
/// Serializes the workbook into an XLSX file encrypted with the given password
|
||||||
/// and loads the bytes into a file named filename.
|
/// and loads the bytes into a file named filename.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void save(const std::string &filename, const std::string &password);
|
void save(const std::string &filename, const std::string &password) const;
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serializes the workbook into an XLSX file and saves the data into a file
|
/// Serializes the workbook into an XLSX file and saves the data into a file
|
||||||
/// named filename.
|
/// named filename.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void save(const std::wstring &filename);
|
void save(const std::wstring &filename) const;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serializes the workbook into an XLSX file encrypted with the given password
|
/// Serializes the workbook into an XLSX file encrypted with the given password
|
||||||
/// and loads the bytes into a file named filename.
|
/// and loads the bytes into a file named filename.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void save(const std::wstring &filename, const std::string &password);
|
void save(const std::wstring &filename, const std::string &password) const;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -476,7 +476,7 @@ public:
|
||||||
/// Serializes the workbook into an XLSX file encrypted with the given password
|
/// Serializes the workbook into an XLSX file encrypted with the given password
|
||||||
/// and loads the bytes into a file named filename.
|
/// and loads the bytes into a file named filename.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void save(const xlnt::path &filename, const std::string &password);
|
void save(const xlnt::path &filename, const std::string &password) const;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serializes the workbook into an XLSX file and saves the data into stream.
|
/// Serializes the workbook into an XLSX file and saves the data into stream.
|
||||||
|
@ -487,7 +487,7 @@ public:
|
||||||
/// Serializes the workbook into an XLSX file encrypted with the given password
|
/// Serializes the workbook into an XLSX file encrypted with the given password
|
||||||
/// and loads the bytes into the given stream.
|
/// and loads the bytes into the given stream.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void save(std::ostream &stream, const std::string &password);
|
void save(std::ostream &stream, const std::string &password) const;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interprets byte vector data as an XLSX file and sets the content of this
|
/// Interprets byte vector data as an XLSX file and sets the content of this
|
||||||
|
|
|
@ -742,6 +742,11 @@ private:
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void register_comments_in_manifest();
|
void register_comments_in_manifest();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add the calcChain part to the workbook if it doesn't already exist.
|
||||||
|
/// </summary>
|
||||||
|
void register_calc_chain_in_manifest();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes calcChain part from manifest if no formulae remain in workbook.
|
/// Removes calcChain part from manifest if no formulae remain in workbook.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -446,18 +446,24 @@ void cell::hyperlink(const std::string &hyperlink)
|
||||||
}
|
}
|
||||||
|
|
||||||
d_->hyperlink_ = hyperlink;
|
d_->hyperlink_ = hyperlink;
|
||||||
|
}
|
||||||
|
|
||||||
if (data_type() == type::null)
|
void cell::hyperlink(const std::string &url, const std::string &display)
|
||||||
{
|
{
|
||||||
value(hyperlink);
|
hyperlink(url);
|
||||||
}
|
value(display);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cell::hyperlink(xlnt::cell /*target*/)
|
||||||
|
{
|
||||||
|
//todo: implement
|
||||||
}
|
}
|
||||||
|
|
||||||
void cell::formula(const std::string &formula)
|
void cell::formula(const std::string &formula)
|
||||||
{
|
{
|
||||||
if (formula.empty())
|
if (formula.empty())
|
||||||
{
|
{
|
||||||
throw invalid_parameter();
|
return clear_formula();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formula[0] == '=')
|
if (formula[0] == '=')
|
||||||
|
@ -468,6 +474,8 @@ void cell::formula(const std::string &formula)
|
||||||
{
|
{
|
||||||
d_->formula_ = formula;
|
d_->formula_ = formula;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
worksheet().register_calc_chain_in_manifest();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cell::has_formula() const
|
bool cell::has_formula() const
|
||||||
|
|
|
@ -140,7 +140,7 @@ public:
|
||||||
auto cell = ws.cell(xlnt::cell_reference(1, 1));
|
auto cell = ws.cell(xlnt::cell_reference(1, 1));
|
||||||
|
|
||||||
TS_ASSERT(!cell.has_formula());
|
TS_ASSERT(!cell.has_formula());
|
||||||
TS_ASSERT_THROWS(cell.formula(""), xlnt::invalid_parameter);
|
TS_ASSERT_THROWS_NOTHING(cell.formula(""));
|
||||||
TS_ASSERT(!cell.has_formula());
|
TS_ASSERT(!cell.has_formula());
|
||||||
cell.formula("=42");
|
cell.formula("=42");
|
||||||
TS_ASSERT(cell.has_formula());
|
TS_ASSERT(cell.has_formula());
|
||||||
|
|
|
@ -95,9 +95,9 @@ struct workbook_impl
|
||||||
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<xlnt::core_property, variant, xlnt::scoped_enum_hash<xlnt::core_property>> core_properties_;
|
std::vector<std::pair<xlnt::core_property, variant>> core_properties_;
|
||||||
std::unordered_map<xlnt::extended_property, variant, xlnt::scoped_enum_hash<xlnt::extended_property>> extended_properties_;
|
std::vector<std::pair<xlnt::extended_property, variant>> extended_properties_;
|
||||||
std::unordered_map<std::string, variant> custom_properties_;
|
std::vector<std::pair<std::string, variant>> custom_properties_;
|
||||||
|
|
||||||
std::unordered_map<std::string, std::string> sheet_title_rel_id_map_;
|
std::unordered_map<std::string, std::string> sheet_title_rel_id_map_;
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
162
source/detail/xlsx_crypto.hpp
Normal file
162
source/detail/xlsx_crypto.hpp
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
// Copyright (c) 2014-2017 Thomas Fussell
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, WRISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE
|
||||||
|
//
|
||||||
|
// @license: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
// @author: see AUTHORS file
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include <xlnt/utils/exceptions.hpp>
|
||||||
|
#include <xlnt/workbook/workbook.hpp>
|
||||||
|
#include <detail/constants.hpp>
|
||||||
|
#include <detail/include_cryptopp.hpp>
|
||||||
|
#include <detail/include_libstudxml.hpp>
|
||||||
|
#include <detail/pole.hpp>
|
||||||
|
#include <detail/vector_streambuf.hpp>
|
||||||
|
#include <detail/xlsx_consumer.hpp>
|
||||||
|
#include <detail/xlsx_producer.hpp>
|
||||||
|
|
||||||
|
namespace xlnt {
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
enum class hash_algorithm
|
||||||
|
{
|
||||||
|
sha1,
|
||||||
|
sha256,
|
||||||
|
sha384,
|
||||||
|
sha512,
|
||||||
|
md5,
|
||||||
|
md4,
|
||||||
|
md2,
|
||||||
|
ripemd128,
|
||||||
|
ripemd160,
|
||||||
|
whirlpool
|
||||||
|
};
|
||||||
|
|
||||||
|
struct crypto_helper
|
||||||
|
{
|
||||||
|
static const std::size_t segment_length;
|
||||||
|
|
||||||
|
enum class cipher_algorithm
|
||||||
|
{
|
||||||
|
aes,
|
||||||
|
rc2,
|
||||||
|
rc4,
|
||||||
|
des,
|
||||||
|
desx,
|
||||||
|
triple_des,
|
||||||
|
triple_des_112
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class cipher_chaining
|
||||||
|
{
|
||||||
|
ecb, // electronic code book
|
||||||
|
cbc, // cipher block chaining
|
||||||
|
cfb // cipher feedback chaining
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class cipher_direction
|
||||||
|
{
|
||||||
|
encryption,
|
||||||
|
decryption
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<std::uint8_t> aes(const std::vector<std::uint8_t> &key, const std::vector<std::uint8_t> &iv,
|
||||||
|
const std::vector<std::uint8_t> &source, cipher_chaining chaining, cipher_direction direction);
|
||||||
|
|
||||||
|
static std::vector<std::uint8_t> decode_base64(const std::string &encoded);
|
||||||
|
|
||||||
|
static std::string encode_base64(const std::vector<std::uint8_t> &decoded);
|
||||||
|
|
||||||
|
static std::vector<std::uint8_t> hash(hash_algorithm algorithm, const std::vector<std::uint8_t> &input);
|
||||||
|
|
||||||
|
static std::vector<std::uint8_t> file(POLE::Storage &storage, const std::string &name);
|
||||||
|
|
||||||
|
struct standard_encryption_info
|
||||||
|
{
|
||||||
|
const std::size_t spin_count = 50000;
|
||||||
|
std::size_t block_size;
|
||||||
|
std::size_t key_bits;
|
||||||
|
std::size_t key_bytes;
|
||||||
|
std::size_t hash_size;
|
||||||
|
cipher_algorithm cipher;
|
||||||
|
cipher_chaining chaining;
|
||||||
|
const hash_algorithm hash = hash_algorithm::sha1;
|
||||||
|
std::vector<std::uint8_t> salt_value;
|
||||||
|
std::vector<std::uint8_t> verifier_hash_input;
|
||||||
|
std::vector<std::uint8_t> verifier_hash_value;
|
||||||
|
std::vector<std::uint8_t> encrypted_key_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<std::uint8_t> decrypt_xlsx_standard(const std::vector<std::uint8_t> &encryption_info,
|
||||||
|
const std::string &password, const std::vector<std::uint8_t> &encrypted_package);
|
||||||
|
|
||||||
|
struct agile_encryption_info
|
||||||
|
{
|
||||||
|
// key data
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
std::size_t salt_size;
|
||||||
|
std::size_t block_size;
|
||||||
|
std::size_t key_bits;
|
||||||
|
std::size_t hash_size;
|
||||||
|
std::string cipher_algorithm;
|
||||||
|
std::string cipher_chaining;
|
||||||
|
std::string hash_algorithm;
|
||||||
|
std::vector<std::uint8_t> salt_value;
|
||||||
|
} key_data;
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> hmac_key;
|
||||||
|
std::vector<std::uint8_t> hmac_value;
|
||||||
|
} data_integrity;
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
std::size_t spin_count;
|
||||||
|
std::size_t salt_size;
|
||||||
|
std::size_t block_size;
|
||||||
|
std::size_t key_bits;
|
||||||
|
std::size_t hash_size;
|
||||||
|
std::string cipher_algorithm;
|
||||||
|
std::string cipher_chaining;
|
||||||
|
hash_algorithm hash;
|
||||||
|
std::vector<std::uint8_t> salt_value;
|
||||||
|
std::vector<std::uint8_t> verifier_hash_input;
|
||||||
|
std::vector<std::uint8_t> verifier_hash_value;
|
||||||
|
std::vector<std::uint8_t> encrypted_key_value;
|
||||||
|
} key_encryptor;
|
||||||
|
};
|
||||||
|
|
||||||
|
static agile_encryption_info generate_agile_encryption_info(const std::string &password);
|
||||||
|
|
||||||
|
static std::vector<std::uint8_t> write_agile_encryption_info(const std::string &password);
|
||||||
|
|
||||||
|
static std::vector<std::uint8_t> decrypt_xlsx_agile(const std::vector<std::uint8_t> &encryption_info,
|
||||||
|
const std::string &password, const std::vector<std::uint8_t> &encrypted_package);
|
||||||
|
|
||||||
|
static std::vector<std::uint8_t> decrypt_xlsx(const std::vector<std::uint8_t> &bytes, const std::string &password);
|
||||||
|
|
||||||
|
static std::vector<std::uint8_t> encrypt_xlsx(const std::vector<std::uint8_t> &bytes, const std::string &password);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
} // namespace xlnt
|
|
@ -218,7 +218,7 @@ void xlsx_producer::write_property(const std::string &name, const variant &value
|
||||||
write_start_element(constants::ns("vt"), "bool");
|
write_start_element(constants::ns("vt"), "bool");
|
||||||
}
|
}
|
||||||
|
|
||||||
write_characters(write_bool(value.get<bool>()));
|
write_characters(value.get<bool>() ? "true" : "false");
|
||||||
|
|
||||||
if (custom)
|
if (custom)
|
||||||
{
|
{
|
||||||
|
@ -336,6 +336,7 @@ void xlsx_producer::write_core_properties(const relationship &/*rel*/)
|
||||||
for (const auto &ns : core_property_namespace(prop))
|
for (const auto &ns : core_property_namespace(prop))
|
||||||
{
|
{
|
||||||
if (namespaces.count(ns.first) > 0) continue;
|
if (namespaces.count(ns.first) > 0) continue;
|
||||||
|
|
||||||
write_namespace(ns.first, ns.second);
|
write_namespace(ns.first, ns.second);
|
||||||
namespaces.emplace(ns);
|
namespaces.emplace(ns);
|
||||||
}
|
}
|
||||||
|
|
|
@ -432,7 +432,7 @@ protected:
|
||||||
|
|
||||||
virtual int underflow()
|
virtual int underflow()
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Attempt to read write only ostream");
|
throw xlnt::exception("Attempt to read write only ostream");
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual int overflow(int c = EOF);
|
virtual int overflow(int c = EOF);
|
||||||
|
@ -494,7 +494,7 @@ izstream::izstream(std::istream &stream)
|
||||||
{
|
{
|
||||||
if (!stream)
|
if (!stream)
|
||||||
{
|
{
|
||||||
throw std::runtime_error("ZIP: Invalid file handle");
|
throw xlnt::exception("Invalid file handle");
|
||||||
}
|
}
|
||||||
|
|
||||||
read_central_header();
|
read_central_header();
|
||||||
|
@ -522,15 +522,20 @@ bool izstream::read_central_header()
|
||||||
}
|
}
|
||||||
|
|
||||||
source_stream_.seekg(end_position - read_start);
|
source_stream_.seekg(end_position - read_start);
|
||||||
std::vector<char> buf(static_cast<std::size_t>(read_start), '\0');
|
std::vector<std::uint8_t> buf(static_cast<std::size_t>(read_start), '\0');
|
||||||
|
|
||||||
if (read_start <= 0)
|
if (read_start <= 0)
|
||||||
{
|
{
|
||||||
std::cerr << "ZIP: Invalid read buffer size" << std::endl;
|
throw xlnt::exception("file is empty");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
source_stream_.read(buf.data(), read_start);
|
source_stream_.read(reinterpret_cast<char *>(buf.data()), read_start);
|
||||||
|
|
||||||
|
if (buf[0] == 0xd0 && buf[1] == 0xcf && buf[2] == 0x11 && buf[3] == 0xe0
|
||||||
|
&& buf[4] == 0xa1 && buf[5] == 0xb1 && buf[6] == 0x1a && buf[7] == 0xe1)
|
||||||
|
{
|
||||||
|
throw xlnt::exception("encrypted xlsx, password required");
|
||||||
|
}
|
||||||
|
|
||||||
auto found_header = false;
|
auto found_header = false;
|
||||||
std::size_t header_index = 0;
|
std::size_t header_index = 0;
|
||||||
|
@ -547,8 +552,7 @@ bool izstream::read_central_header()
|
||||||
|
|
||||||
if (!found_header)
|
if (!found_header)
|
||||||
{
|
{
|
||||||
std::cerr << "ZIP: Failed to find zip header" << std::endl;
|
throw xlnt::exception("failed to find zip header");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// seek to end of central header and read
|
// seek to end of central header and read
|
||||||
|
@ -560,8 +564,7 @@ bool izstream::read_central_header()
|
||||||
|
|
||||||
if (disk_number1 != disk_number2 || disk_number1 != 0)
|
if (disk_number1 != disk_number2 || disk_number1 != 0)
|
||||||
{
|
{
|
||||||
std::cerr << "ZIP: multiple disk zip files are not supported" << std::endl;
|
throw xlnt::exception("multiple disk zip files are not supported");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto num_files = read_int<std::uint16_t>(source_stream_); // one entry in center in this disk
|
auto num_files = read_int<std::uint16_t>(source_stream_); // one entry in center in this disk
|
||||||
|
@ -569,8 +572,7 @@ bool izstream::read_central_header()
|
||||||
|
|
||||||
if (num_files != num_files_this_disk)
|
if (num_files != num_files_this_disk)
|
||||||
{
|
{
|
||||||
std::cerr << "ZIP: multi disk zip files are not supported" << std::endl;
|
throw xlnt::exception("multi disk zip files are not supported");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*auto size_of_header = */ read_int<std::uint32_t>(source_stream_); // size of header
|
/*auto size_of_header = */ read_int<std::uint32_t>(source_stream_); // size of header
|
||||||
|
@ -592,7 +594,7 @@ std::unique_ptr<std::streambuf> izstream::open(const path &filename) const
|
||||||
{
|
{
|
||||||
if (!has_file(filename))
|
if (!has_file(filename))
|
||||||
{
|
{
|
||||||
throw "not found";
|
throw xlnt::exception("file not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto header = file_headers_.at(filename.string());
|
auto header = file_headers_.at(filename.string());
|
||||||
|
|
|
@ -134,7 +134,7 @@ public:
|
||||||
TS_ASSERT(!temp_buffer.empty());
|
TS_ASSERT(!temp_buffer.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_write_comments()
|
void test_write_comments_hyperlinks_formulae()
|
||||||
{
|
{
|
||||||
xlnt::workbook wb;
|
xlnt::workbook wb;
|
||||||
auto sheet1 = wb.active_sheet();
|
auto sheet1 = wb.active_sheet();
|
||||||
|
@ -146,6 +146,15 @@ public:
|
||||||
sheet1.cell("A2").value("Sheet1!A2");
|
sheet1.cell("A2").value("Sheet1!A2");
|
||||||
sheet1.cell("A2").comment("Sheet1 comment2", comment_font, "Microsoft Office User");
|
sheet1.cell("A2").comment("Sheet1 comment2", comment_font, "Microsoft Office User");
|
||||||
|
|
||||||
|
sheet1.cell("A4").hyperlink("https://microsoft.com", "hyperlink1");
|
||||||
|
sheet1.cell("A5").hyperlink("https://google.com");
|
||||||
|
sheet1.cell("A6").hyperlink(sheet1.cell("A1"));
|
||||||
|
sheet1.cell("A7").hyperlink("mailto:invalid@example.com?subject=important");
|
||||||
|
|
||||||
|
sheet1.cell("C1").formula("=CONCATENATE(C2,C3)");
|
||||||
|
sheet1.cell("C2").value("a");
|
||||||
|
sheet1.cell("C3").value("b");
|
||||||
|
|
||||||
auto sheet2 = wb.create_sheet();
|
auto sheet2 = wb.create_sheet();
|
||||||
sheet2.cell("A1").value("Sheet2!A1");
|
sheet2.cell("A1").value("Sheet2!A1");
|
||||||
sheet2.cell("A2").comment("Sheet2 comment", comment_font, "Microsoft Office User");
|
sheet2.cell("A2").comment("Sheet2 comment", comment_font, "Microsoft Office User");
|
||||||
|
@ -153,6 +162,12 @@ public:
|
||||||
sheet2.cell("A2").value("Sheet2!A2");
|
sheet2.cell("A2").value("Sheet2!A2");
|
||||||
sheet2.cell("A2").comment("Sheet2 comment2", comment_font, "Microsoft Office User");
|
sheet2.cell("A2").comment("Sheet2 comment2", comment_font, "Microsoft Office User");
|
||||||
|
|
||||||
|
sheet2.cell("A4").hyperlink("https://apple.com", "hyperlink2");
|
||||||
|
|
||||||
|
sheet2.cell("C1").formula("=C2*C3");
|
||||||
|
sheet2.cell("C2").value(2);
|
||||||
|
sheet2.cell("C3").value(3);
|
||||||
|
|
||||||
const auto path = path_helper::data_directory("10_comments_hyperlinks_formulae.xlsx");
|
const auto path = path_helper::data_directory("10_comments_hyperlinks_formulae.xlsx");
|
||||||
TS_ASSERT(workbook_matches_file(wb, path));
|
TS_ASSERT(workbook_matches_file(wb, path));
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <cxxtest/TestSuite.h>
|
#include <cxxtest/TestSuite.h>
|
||||||
|
|
||||||
#include <detail/vector_streambuf.hpp>
|
#include <detail/vector_streambuf.hpp>
|
||||||
|
#include <detail/xlsx_crypto.hpp>
|
||||||
#include <helpers/path_helper.hpp>
|
#include <helpers/path_helper.hpp>
|
||||||
#include <helpers/xml_helper.hpp>
|
#include <helpers/xml_helper.hpp>
|
||||||
#include <xlnt/workbook/workbook.hpp>
|
#include <xlnt/workbook/workbook.hpp>
|
||||||
|
@ -16,27 +17,34 @@ public:
|
||||||
/// Read file as an XLSX-formatted ZIP file in the filesystem to a workbook,
|
/// Read file as an XLSX-formatted ZIP file in the filesystem to a workbook,
|
||||||
/// write the workbook back to memory, then ensure that the contents of the two files are equivalent.
|
/// write the workbook back to memory, then ensure that the contents of the two files are equivalent.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool round_trip_matches_rw(const xlnt::path &original)
|
bool round_trip_matches_rw(const xlnt::path &source)
|
||||||
{
|
{
|
||||||
std::ifstream file_stream(original.string(), std::ios::binary);
|
xlnt::workbook source_workbook;
|
||||||
std::vector<std::uint8_t> original_data;
|
source_workbook.load(source);
|
||||||
|
|
||||||
{
|
std::vector<std::uint8_t> destination;
|
||||||
xlnt::detail::vector_ostreambuf file_data_buffer(original_data);
|
source_workbook.save(destination);
|
||||||
std::ostream file_data_stream(&file_data_buffer);
|
|
||||||
file_data_stream << file_stream.rdbuf();
|
|
||||||
}
|
|
||||||
|
|
||||||
xlnt::workbook original_workbook;
|
std::ifstream source_stream(source.string(), std::ios::binary);
|
||||||
original_workbook.load(original);
|
|
||||||
|
|
||||||
std::vector<std::uint8_t> buffer;
|
|
||||||
original_workbook.save(buffer);
|
|
||||||
original_workbook.save("round_trip_out.xlsx");
|
|
||||||
|
|
||||||
return xml_helper::xlsx_archives_match(original_data, buffer);
|
return xml_helper::xlsx_archives_match(xlnt::detail::to_vector(source_stream), destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool round_trip_matches_rw(const xlnt::path &source, const std::string &password)
|
||||||
|
{
|
||||||
|
xlnt::workbook source_workbook;
|
||||||
|
source_workbook.load(source, password);
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> destination;
|
||||||
|
source_workbook.save(destination);
|
||||||
|
|
||||||
|
std::ifstream source_stream(source.string(), std::ios::binary);
|
||||||
|
const auto source_decrypted = xlnt::detail::crypto_helper::decrypt_xlsx(
|
||||||
|
xlnt::detail::to_vector(source_stream), password);
|
||||||
|
|
||||||
|
return xml_helper::xlsx_archives_match(source_decrypted, destination);
|
||||||
|
}
|
||||||
|
|
||||||
void test_round_trip_empty_excel_rw()
|
void test_round_trip_empty_excel_rw()
|
||||||
{
|
{
|
||||||
const auto files = std::vector<std::string>
|
const auto files = std::vector<std::string>
|
||||||
|
@ -44,10 +52,6 @@ public:
|
||||||
"2_minimal",
|
"2_minimal",
|
||||||
"3_default",
|
"3_default",
|
||||||
"4_every_style",
|
"4_every_style",
|
||||||
"5_encrypted_agile",
|
|
||||||
"6_encrypted_libre",
|
|
||||||
"7_encrypted_standard",
|
|
||||||
"8_encrypted_numbers",
|
|
||||||
"10_comments_hyperlinks_formulae",
|
"10_comments_hyperlinks_formulae",
|
||||||
"11_print_settings",
|
"11_print_settings",
|
||||||
"12_advanced_properties"
|
"12_advanced_properties"
|
||||||
|
@ -59,4 +63,22 @@ public:
|
||||||
TS_ASSERT(round_trip_matches_rw(path));
|
TS_ASSERT(round_trip_matches_rw(path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_round_trip_empty_excel_rw_encrypted()
|
||||||
|
{
|
||||||
|
const auto files = std::vector<std::string>
|
||||||
|
{
|
||||||
|
"5_encrypted_agile",
|
||||||
|
"6_encrypted_libre",
|
||||||
|
"7_encrypted_standard",
|
||||||
|
"8_encrypted_numbers"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto file : files)
|
||||||
|
{
|
||||||
|
auto path = path_helper::data_directory(file + ".xlsx");
|
||||||
|
TS_ASSERT(round_trip_matches_rw(path, file
|
||||||
|
== "7_encrypted_standard" ? "password" : "secret"));
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -107,9 +107,9 @@ void open_stream(std::ofstream &stream, const std::string &path)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
std::vector<typename T::key_type> keys(const T &container)
|
std::vector<T> keys(const std::vector<std::pair<T, xlnt::variant>> &container)
|
||||||
{
|
{
|
||||||
auto result = std::vector<typename T::key_type>();
|
auto result = std::vector<T>();
|
||||||
auto iter = container.begin();
|
auto iter = container.begin();
|
||||||
|
|
||||||
while (iter != container.end())
|
while (iter != container.end())
|
||||||
|
@ -120,6 +120,20 @@ std::vector<typename T::key_type> keys(const T &container)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool contains(const std::vector<std::pair<T, xlnt::variant>> &container, const T key)
|
||||||
|
{
|
||||||
|
for (const auto &iter : container)
|
||||||
|
{
|
||||||
|
if (iter.first == key)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
xlnt::path default_path(xlnt::relationship_type type, std::size_t index = 0)
|
xlnt::path default_path(xlnt::relationship_type type, std::size_t index = 0)
|
||||||
{
|
{
|
||||||
using xlnt::path;
|
using xlnt::path;
|
||||||
|
@ -285,7 +299,7 @@ namespace xlnt {
|
||||||
|
|
||||||
bool workbook::has_core_property(xlnt::core_property type) const
|
bool workbook::has_core_property(xlnt::core_property type) const
|
||||||
{
|
{
|
||||||
return d_->core_properties_.count(type) > 0;
|
return ::contains(d_->core_properties_, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<xlnt::core_property> workbook::core_properties() const
|
std::vector<xlnt::core_property> workbook::core_properties() const
|
||||||
|
@ -295,18 +309,36 @@ std::vector<xlnt::core_property> workbook::core_properties() const
|
||||||
|
|
||||||
variant workbook::core_property(xlnt::core_property type) const
|
variant workbook::core_property(xlnt::core_property type) const
|
||||||
{
|
{
|
||||||
return d_->core_properties_.at(type);
|
for (auto iter : d_->core_properties_)
|
||||||
|
{
|
||||||
|
if (iter.first == type)
|
||||||
|
{
|
||||||
|
return iter.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw xlnt::exception("workbook doesn't have core property");
|
||||||
}
|
}
|
||||||
|
|
||||||
void workbook::core_property(xlnt::core_property type, const variant &value)
|
void workbook::core_property(xlnt::core_property type, const variant &value)
|
||||||
{
|
{
|
||||||
register_package_part(relationship_type::core_properties);
|
register_package_part(relationship_type::core_properties);
|
||||||
d_->core_properties_[type] = value;
|
|
||||||
|
for (auto &iter : d_->core_properties_)
|
||||||
|
{
|
||||||
|
if (iter.first == type)
|
||||||
|
{
|
||||||
|
iter.second = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d_->core_properties_.push_back({type, value});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool workbook::has_extended_property(xlnt::extended_property type) const
|
bool workbook::has_extended_property(xlnt::extended_property type) const
|
||||||
{
|
{
|
||||||
return d_->extended_properties_.count(type) > 0;
|
return ::contains(d_->extended_properties_, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<xlnt::extended_property> workbook::extended_properties() const
|
std::vector<xlnt::extended_property> workbook::extended_properties() const
|
||||||
|
@ -317,17 +349,35 @@ std::vector<xlnt::extended_property> workbook::extended_properties() const
|
||||||
void workbook::extended_property(xlnt::extended_property type, const variant &value)
|
void workbook::extended_property(xlnt::extended_property type, const variant &value)
|
||||||
{
|
{
|
||||||
register_package_part(relationship_type::extended_properties);
|
register_package_part(relationship_type::extended_properties);
|
||||||
d_->extended_properties_[type] = value;
|
|
||||||
|
for (auto &iter : d_->extended_properties_)
|
||||||
|
{
|
||||||
|
if (iter.first == type)
|
||||||
|
{
|
||||||
|
iter.second = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d_->extended_properties_.push_back({type, value});
|
||||||
}
|
}
|
||||||
|
|
||||||
variant workbook::extended_property(xlnt::extended_property type) const
|
variant workbook::extended_property(xlnt::extended_property type) const
|
||||||
{
|
{
|
||||||
return d_->extended_properties_.at(type);
|
for (auto iter : d_->extended_properties_)
|
||||||
|
{
|
||||||
|
if (iter.first == type)
|
||||||
|
{
|
||||||
|
return iter.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw xlnt::exception("workbook doesn't have extended property");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool workbook::has_custom_property(const std::string &property_name) const
|
bool workbook::has_custom_property(const std::string &property_name) const
|
||||||
{
|
{
|
||||||
return d_->custom_properties_.count(property_name) > 0;
|
return ::contains(d_->custom_properties_, property_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> workbook::custom_properties() const
|
std::vector<std::string> workbook::custom_properties() const
|
||||||
|
@ -338,12 +388,30 @@ std::vector<std::string> workbook::custom_properties() const
|
||||||
void workbook::custom_property(const std::string &property_name, const variant &value)
|
void workbook::custom_property(const std::string &property_name, const variant &value)
|
||||||
{
|
{
|
||||||
register_package_part(relationship_type::custom_properties);
|
register_package_part(relationship_type::custom_properties);
|
||||||
d_->custom_properties_[property_name] = value;
|
|
||||||
|
for (auto &iter : d_->custom_properties_)
|
||||||
|
{
|
||||||
|
if (iter.first == property_name)
|
||||||
|
{
|
||||||
|
iter.second = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d_->custom_properties_.push_back({property_name, value});
|
||||||
}
|
}
|
||||||
|
|
||||||
variant workbook::custom_property(const std::string &property_name) const
|
variant workbook::custom_property(const std::string &property_name) const
|
||||||
{
|
{
|
||||||
return d_->custom_properties_.at(property_name);
|
for (auto iter : d_->custom_properties_)
|
||||||
|
{
|
||||||
|
if (iter.first == property_name)
|
||||||
|
{
|
||||||
|
return iter.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw xlnt::exception("workbook doesn't have custom property");
|
||||||
}
|
}
|
||||||
|
|
||||||
workbook workbook::empty()
|
workbook workbook::empty()
|
||||||
|
@ -367,7 +435,9 @@ workbook workbook::empty()
|
||||||
|
|
||||||
wb.extended_property(xlnt::extended_property::application, "Microsoft Macintosh Excel");
|
wb.extended_property(xlnt::extended_property::application, "Microsoft Macintosh Excel");
|
||||||
wb.extended_property(xlnt::extended_property::doc_security, 0);
|
wb.extended_property(xlnt::extended_property::doc_security, 0);
|
||||||
wb.extended_property(xlnt::extended_property::scale_crop, "false");
|
wb.extended_property(xlnt::extended_property::scale_crop, false);
|
||||||
|
wb.extended_property(xlnt::extended_property::heading_pairs, std::vector<variant>{variant("Worksheets"), variant(1)});
|
||||||
|
wb.extended_property(xlnt::extended_property::titles_of_parts, {"Sheet1"});
|
||||||
wb.extended_property(xlnt::extended_property::company, "");
|
wb.extended_property(xlnt::extended_property::company, "");
|
||||||
wb.extended_property(xlnt::extended_property::links_up_to_date, false);
|
wb.extended_property(xlnt::extended_property::links_up_to_date, false);
|
||||||
wb.extended_property(xlnt::extended_property::shared_doc, false);
|
wb.extended_property(xlnt::extended_property::shared_doc, false);
|
||||||
|
@ -793,25 +863,6 @@ void workbook::load(const std::vector<std::uint8_t> &data)
|
||||||
|
|
||||||
void workbook::load(const std::string &filename)
|
void workbook::load(const std::string &filename)
|
||||||
{
|
{
|
||||||
if (filename.find_last_of(".") != std::string::npos) // check extension
|
|
||||||
{
|
|
||||||
std::string file_format = path(filename).extension();
|
|
||||||
|
|
||||||
if (file_format == "xls") {
|
|
||||||
throw xlnt::exception(" xlnt does not support the old .xls file format");
|
|
||||||
}
|
|
||||||
else if (file_format == "xlsb") {
|
|
||||||
throw xlnt::exception(" xlnt does not support the .xlsb file format");
|
|
||||||
}
|
|
||||||
else if (file_format != "xlsx") {
|
|
||||||
throw xlnt::exception(" xlnt does not support the ."+file_format+ " file format");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw xlnt::exception("file has no extension .xlsx");
|
|
||||||
}
|
|
||||||
|
|
||||||
return load(path(filename));
|
return load(path(filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -830,25 +881,6 @@ void workbook::load(const path &filename)
|
||||||
|
|
||||||
void workbook::load(const std::string &filename, const std::string &password)
|
void workbook::load(const std::string &filename, const std::string &password)
|
||||||
{
|
{
|
||||||
if (filename.find_last_of(".") != std::string::npos) // check extension
|
|
||||||
{
|
|
||||||
std::string file_format = path(filename).extension();
|
|
||||||
|
|
||||||
if (file_format == "xls") {
|
|
||||||
throw xlnt::exception(" xlnt does not support the old .xls file format");
|
|
||||||
}
|
|
||||||
else if (file_format == "xlsb") {
|
|
||||||
throw xlnt::exception(" xlnt does not support the .xlsb file format");
|
|
||||||
}
|
|
||||||
else if (file_format != "xlsx") {
|
|
||||||
throw xlnt::exception(" xlnt does not support the ."+file_format+ " file format");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw xlnt::exception("file has no extension .xlsx");
|
|
||||||
}
|
|
||||||
|
|
||||||
return load(path(filename), password);
|
return load(path(filename), password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -891,9 +923,21 @@ void workbook::save(std::vector<std::uint8_t> &data) const
|
||||||
save(data_stream);
|
save(data_stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void workbook::save(std::vector<std::uint8_t> &data, const std::string &password) const
|
||||||
|
{
|
||||||
|
xlnt::detail::vector_ostreambuf data_buffer(data);
|
||||||
|
std::ostream data_stream(&data_buffer);
|
||||||
|
save(data_stream, password);
|
||||||
|
}
|
||||||
|
|
||||||
void workbook::save(const std::string &filename) const
|
void workbook::save(const std::string &filename) const
|
||||||
{
|
{
|
||||||
return save(path(filename));
|
save(path(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
void workbook::save(const std::string &filename, const std::string &password) const
|
||||||
|
{
|
||||||
|
save(path(filename), password);
|
||||||
}
|
}
|
||||||
|
|
||||||
void workbook::save(const path &filename) const
|
void workbook::save(const path &filename) const
|
||||||
|
@ -903,27 +947,34 @@ void workbook::save(const path &filename) const
|
||||||
save(file_stream);
|
save(file_stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void workbook::save(const path &filename, const std::string &password) const
|
||||||
|
{
|
||||||
|
std::ofstream file_stream;
|
||||||
|
open_stream(file_stream, filename.string());
|
||||||
|
save(file_stream, password);
|
||||||
|
}
|
||||||
|
|
||||||
void workbook::save(std::ostream &stream) const
|
void workbook::save(std::ostream &stream) const
|
||||||
{
|
{
|
||||||
detail::xlsx_producer producer(*this);
|
detail::xlsx_producer producer(*this);
|
||||||
producer.write(stream);
|
producer.write(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
void workbook::save(std::ostream &stream, const std::string &password)
|
void workbook::save(std::ostream &stream, const std::string &password) const
|
||||||
{
|
{
|
||||||
detail::xlsx_producer producer(*this);
|
detail::xlsx_producer producer(*this);
|
||||||
producer.write(stream, password);
|
producer.write(stream, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
void workbook::save(const std::wstring &filename)
|
void workbook::save(const std::wstring &filename) const
|
||||||
{
|
{
|
||||||
std::ofstream file_stream;
|
std::ofstream file_stream;
|
||||||
open_stream(file_stream, filename);
|
open_stream(file_stream, filename);
|
||||||
save(file_stream);
|
save(file_stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
void workbook::save(const std::wstring &filename, const std::string &password)
|
void workbook::save(const std::wstring &filename, const std::string &password) const
|
||||||
{
|
{
|
||||||
std::ofstream file_stream;
|
std::ofstream file_stream;
|
||||||
open_stream(file_stream, filename);
|
open_stream(file_stream, filename);
|
||||||
|
@ -1402,33 +1453,21 @@ std::size_t workbook::rup_build() const
|
||||||
return d_->file_version_.get().rup_build;
|
return d_->file_version_.get().rup_build;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
bool workbook::has_calculation_properties() const
|
bool workbook::has_calculation_properties() const
|
||||||
{
|
{
|
||||||
return d_->calculation_properties_.is_set();
|
return d_->calculation_properties_.is_set();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
class calculation_properties workbook::calculation_properties() const
|
class calculation_properties workbook::calculation_properties() const
|
||||||
{
|
{
|
||||||
return d_->calculation_properties_.get();
|
return d_->calculation_properties_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
void workbook::calculation_properties(const class calculation_properties &props)
|
void workbook::calculation_properties(const class calculation_properties &props)
|
||||||
{
|
{
|
||||||
d_->calculation_properties_ = props;
|
d_->calculation_properties_ = props;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes calcChain part from manifest if no formulae remain in workbook.
|
|
||||||
/// </summary>
|
|
||||||
void workbook::garbage_collect_formulae()
|
void workbook::garbage_collect_formulae()
|
||||||
{
|
{
|
||||||
auto any_with_formula = false;
|
auto any_with_formula = false;
|
||||||
|
|
|
@ -222,7 +222,8 @@ public:
|
||||||
xlnt::workbook wb;
|
xlnt::workbook wb;
|
||||||
auto ws = wb.active_sheet();
|
auto ws = wb.active_sheet();
|
||||||
ws.cell("A1").hyperlink("http://test.com");
|
ws.cell("A1").hyperlink("http://test.com");
|
||||||
TS_ASSERT_EQUALS("http://test.com", ws.cell("A1").value<std::string>());
|
TS_ASSERT_EQUALS(ws.cell("A1").hyperlink(), "http://test.com");
|
||||||
|
TS_ASSERT_EQUALS(ws.cell("A1").value<std::string>(), "");
|
||||||
ws.cell("A1").value("test");
|
ws.cell("A1").value("test");
|
||||||
TS_ASSERT_EQUALS("test", ws.cell("A1").value<std::string>());
|
TS_ASSERT_EQUALS("test", ws.cell("A1").value<std::string>());
|
||||||
TS_ASSERT_EQUALS(ws.cell("A1").hyperlink(), "http://test.com");
|
TS_ASSERT_EQUALS(ws.cell("A1").hyperlink(), "http://test.com");
|
||||||
|
|
|
@ -1010,6 +1010,11 @@ void worksheet::register_comments_in_manifest()
|
||||||
workbook().register_worksheet_part(*this, relationship_type::comments);
|
workbook().register_worksheet_part(*this, relationship_type::comments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void worksheet::register_calc_chain_in_manifest()
|
||||||
|
{
|
||||||
|
workbook().register_workbook_part(relationship_type::calculation_chain);
|
||||||
|
}
|
||||||
|
|
||||||
bool worksheet::has_header_footer() const
|
bool worksheet::has_header_footer() const
|
||||||
{
|
{
|
||||||
return d_->header_footer_.is_set();
|
return d_->header_footer_.is_set();
|
||||||
|
|
|
@ -16,6 +16,8 @@ public:
|
||||||
{
|
{
|
||||||
// content types are stored in unordered maps, too complicated to compare
|
// content types are stored in unordered maps, too complicated to compare
|
||||||
if (content_type == "[Content_Types].xml") return true;
|
if (content_type == "[Content_Types].xml") return true;
|
||||||
|
// calcChain is optional
|
||||||
|
if (content_type == "application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml") return true;
|
||||||
|
|
||||||
auto is_xml = (content_type.substr(0, 12) == "application/"
|
auto is_xml = (content_type.substr(0, 12) == "application/"
|
||||||
&& content_type.substr(content_type.size() - 4) == "+xml")
|
&& content_type.substr(content_type.size() - 4) == "+xml")
|
||||||
|
@ -200,7 +202,7 @@ public:
|
||||||
std::cout << "right is missing file: " << left_member.string() << std::endl;
|
std::cout << "right is missing file: " << left_member.string() << std::endl;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto left_content_type = left_member.string() == "[Content_Types].xml"
|
auto left_content_type = left_member.string() == "[Content_Types].xml"
|
||||||
? "[Content_Types].xml" : left_manifest.content_type(left_member);
|
? "[Content_Types].xml" : left_manifest.content_type(left_member);
|
||||||
auto right_content_type = left_member.string() == "[Content_Types].xml"
|
auto right_content_type = left_member.string() == "[Content_Types].xml"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user