Merge remote-tracking branch 'upstream/master'

This commit is contained in:
sukoi26 2017-02-18 09:27:06 +01:00
commit 60459bcd25
16 changed files with 905 additions and 732 deletions

View File

@ -183,9 +183,20 @@ public:
std::string hyperlink() const;
/// <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>
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>
/// Returns true if this cell has a hyperlink set.

View File

@ -450,20 +450,20 @@ public:
/// Serializes the workbook into an XLSX file encrypted with the given password
/// and loads the bytes into a file named filename.
/// </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
/// <summary>
/// Serializes the workbook into an XLSX file and saves the data into a file
/// named filename.
/// </summary>
void save(const std::wstring &filename);
void save(const std::wstring &filename) const;
/// <summary>
/// Serializes the workbook into an XLSX file encrypted with the given password
/// and loads the bytes into a file named filename.
/// </summary>
void save(const std::wstring &filename, const std::string &password);
void save(const std::wstring &filename, const std::string &password) const;
#endif
/// <summary>
@ -476,7 +476,7 @@ public:
/// Serializes the workbook into an XLSX file encrypted with the given password
/// and loads the bytes into a file named filename.
/// </summary>
void save(const xlnt::path &filename, const std::string &password);
void save(const xlnt::path &filename, const std::string &password) const;
/// <summary>
/// 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
/// and loads the bytes into the given stream.
/// </summary>
void save(std::ostream &stream, const std::string &password);
void save(std::ostream &stream, const std::string &password) const;
/// <summary>
/// Interprets byte vector data as an XLSX file and sets the content of this

View File

@ -742,6 +742,11 @@ private:
/// </summary>
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>
/// Removes calcChain part from manifest if no formulae remain in workbook.
/// </summary>

View File

@ -446,18 +446,24 @@ void cell::hyperlink(const std::string &hyperlink)
}
d_->hyperlink_ = hyperlink;
}
if (data_type() == type::null)
{
value(hyperlink);
}
void cell::hyperlink(const std::string &url, const std::string &display)
{
hyperlink(url);
value(display);
}
void cell::hyperlink(xlnt::cell /*target*/)
{
//todo: implement
}
void cell::formula(const std::string &formula)
{
if (formula.empty())
{
throw invalid_parameter();
return clear_formula();
}
if (formula[0] == '=')
@ -468,6 +474,8 @@ void cell::formula(const std::string &formula)
{
d_->formula_ = formula;
}
worksheet().register_calc_chain_in_manifest();
}
bool cell::has_formula() const

View File

@ -140,7 +140,7 @@ public:
auto cell = ws.cell(xlnt::cell_reference(1, 1));
TS_ASSERT(!cell.has_formula());
TS_ASSERT_THROWS(cell.formula(""), xlnt::invalid_parameter);
TS_ASSERT_THROWS_NOTHING(cell.formula(""));
TS_ASSERT(!cell.has_formula());
cell.formula("=42");
TS_ASSERT(cell.has_formula());

View File

@ -95,9 +95,9 @@ struct workbook_impl
optional<theme> theme_;
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::unordered_map<xlnt::extended_property, variant, xlnt::scoped_enum_hash<xlnt::extended_property>> extended_properties_;
std::unordered_map<std::string, variant> custom_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<std::string, variant>> custom_properties_;
std::unordered_map<std::string, std::string> sheet_title_rel_id_map_;

File diff suppressed because it is too large Load Diff

View 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

View File

@ -218,7 +218,7 @@ void xlsx_producer::write_property(const std::string &name, const variant &value
write_start_element(constants::ns("vt"), "bool");
}
write_characters(write_bool(value.get<bool>()));
write_characters(value.get<bool>() ? "true" : "false");
if (custom)
{
@ -336,6 +336,7 @@ void xlsx_producer::write_core_properties(const relationship &/*rel*/)
for (const auto &ns : core_property_namespace(prop))
{
if (namespaces.count(ns.first) > 0) continue;
write_namespace(ns.first, ns.second);
namespaces.emplace(ns);
}

View File

@ -432,7 +432,7 @@ protected:
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);
@ -494,7 +494,7 @@ izstream::izstream(std::istream &stream)
{
if (!stream)
{
throw std::runtime_error("ZIP: Invalid file handle");
throw xlnt::exception("Invalid file handle");
}
read_central_header();
@ -522,15 +522,20 @@ bool izstream::read_central_header()
}
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)
{
std::cerr << "ZIP: Invalid read buffer size" << std::endl;
return false;
throw xlnt::exception("file is empty");
}
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;
std::size_t header_index = 0;
@ -547,8 +552,7 @@ bool izstream::read_central_header()
if (!found_header)
{
std::cerr << "ZIP: Failed to find zip header" << std::endl;
return false;
throw xlnt::exception("failed to find zip header");
}
// 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)
{
std::cerr << "ZIP: multiple disk zip files are not supported" << std::endl;
return false;
throw xlnt::exception("multiple disk zip files are not supported");
}
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)
{
std::cerr << "ZIP: multi disk zip files are not supported" << std::endl;
return false;
throw xlnt::exception("multi disk zip files are not supported");
}
/*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))
{
throw "not found";
throw xlnt::exception("file not found");
}
auto header = file_headers_.at(filename.string());

View File

@ -134,7 +134,7 @@ public:
TS_ASSERT(!temp_buffer.empty());
}
void test_write_comments()
void test_write_comments_hyperlinks_formulae()
{
xlnt::workbook wb;
auto sheet1 = wb.active_sheet();
@ -146,6 +146,15 @@ public:
sheet1.cell("A2").value("Sheet1!A2");
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();
sheet2.cell("A1").value("Sheet2!A1");
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").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");
TS_ASSERT(workbook_matches_file(wb, path));
}

View File

@ -5,6 +5,7 @@
#include <cxxtest/TestSuite.h>
#include <detail/vector_streambuf.hpp>
#include <detail/xlsx_crypto.hpp>
#include <helpers/path_helper.hpp>
#include <helpers/xml_helper.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,
/// write the workbook back to memory, then ensure that the contents of the two files are equivalent.
/// </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);
std::vector<std::uint8_t> original_data;
xlnt::workbook source_workbook;
source_workbook.load(source);
{
xlnt::detail::vector_ostreambuf file_data_buffer(original_data);
std::ostream file_data_stream(&file_data_buffer);
file_data_stream << file_stream.rdbuf();
}
std::vector<std::uint8_t> destination;
source_workbook.save(destination);
xlnt::workbook original_workbook;
original_workbook.load(original);
std::vector<std::uint8_t> buffer;
original_workbook.save(buffer);
original_workbook.save("round_trip_out.xlsx");
std::ifstream source_stream(source.string(), std::ios::binary);
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()
{
const auto files = std::vector<std::string>
@ -44,10 +52,6 @@ public:
"2_minimal",
"3_default",
"4_every_style",
"5_encrypted_agile",
"6_encrypted_libre",
"7_encrypted_standard",
"8_encrypted_numbers",
"10_comments_hyperlinks_formulae",
"11_print_settings",
"12_advanced_properties"
@ -59,4 +63,22 @@ public:
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"));
}
}
};

View File

@ -107,9 +107,9 @@ void open_stream(std::ofstream &stream, const std::string &path)
#endif
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();
while (iter != container.end())
@ -120,6 +120,20 @@ std::vector<typename T::key_type> keys(const T &container)
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)
{
using xlnt::path;
@ -285,7 +299,7 @@ namespace xlnt {
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
@ -295,18 +309,36 @@ std::vector<xlnt::core_property> workbook::core_properties() 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)
{
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
{
return d_->extended_properties_.count(type) > 0;
return ::contains(d_->extended_properties_, type);
}
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)
{
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
{
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
{
return d_->custom_properties_.count(property_name) > 0;
return ::contains(d_->custom_properties_, property_name);
}
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)
{
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
{
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()
@ -367,7 +435,9 @@ workbook workbook::empty()
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::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::links_up_to_date, 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)
{
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));
}
@ -830,25 +881,6 @@ void workbook::load(const path &filename)
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);
}
@ -891,9 +923,21 @@ void workbook::save(std::vector<std::uint8_t> &data) const
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
{
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
@ -903,27 +947,34 @@ void workbook::save(const path &filename) const
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
{
detail::xlsx_producer producer(*this);
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);
producer.write(stream, password);
}
#ifdef _MSC_VER
void workbook::save(const std::wstring &filename)
void workbook::save(const std::wstring &filename) const
{
std::ofstream file_stream;
open_stream(file_stream, filename);
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;
open_stream(file_stream, filename);
@ -1402,33 +1453,21 @@ std::size_t workbook::rup_build() const
return d_->file_version_.get().rup_build;
}
/// <summary>
///
/// </summary>
bool workbook::has_calculation_properties() const
{
return d_->calculation_properties_.is_set();
}
/// <summary>
///
/// </summary>
class calculation_properties workbook::calculation_properties() const
{
return d_->calculation_properties_.get();
}
/// <summary>
///
/// </summary>
void workbook::calculation_properties(const class 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()
{
auto any_with_formula = false;

View File

@ -222,7 +222,8 @@ public:
xlnt::workbook wb;
auto ws = wb.active_sheet();
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");
TS_ASSERT_EQUALS("test", ws.cell("A1").value<std::string>());
TS_ASSERT_EQUALS(ws.cell("A1").hyperlink(), "http://test.com");

View File

@ -1010,6 +1010,11 @@ void worksheet::register_comments_in_manifest()
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
{
return d_->header_footer_.is_set();

View File

@ -16,6 +16,8 @@ public:
{
// content types are stored in unordered maps, too complicated to compare
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/"
&& content_type.substr(content_type.size() - 4) == "+xml")
@ -200,7 +202,7 @@ public:
std::cout << "right is missing file: " << left_member.string() << std::endl;
break;
}
auto left_content_type = left_member.string() == "[Content_Types].xml"
? "[Content_Types].xml" : left_manifest.content_type(left_member);
auto right_content_type = left_member.string() == "[Content_Types].xml"