mirror of
https://github.com/tfussell/xlnt.git
synced 2024-03-22 13:11:17 +08:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
60459bcd25
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
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_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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue
Block a user