Merge pull request #310 from Crzyrndm/dev-reorder-wb-relations-PR

Resolves #279
This commit is contained in:
Crzyrndm 2018-07-21 11:13:05 +12:00 committed by GitHub
commit f035b9041e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 213 additions and 131 deletions

View File

@ -115,7 +115,7 @@ public:
/// <summary>
/// Returns a string of the form rId# that identifies the relationship.
/// </summary>
std::string id() const;
const std::string& id() const;
/// <summary>
/// Returns the type of this relationship.
@ -130,12 +130,12 @@ public:
/// <summary>
/// Returns the URI of the package part this relationship points to.
/// </summary>
uri source() const;
const uri &source() const;
/// <summary>
/// Returns the URI of the package part this relationship points to.
/// </summary>
uri target() const;
const uri &target() const;
/// <summary>
/// Returns true if and only if rhs is equal to this relationship.

View File

@ -123,7 +123,7 @@ public:
/// Returns the path of this URI.
/// E.g. the path of http://example.com/document is "/document"
/// </summary>
class path path() const;
const class path& path() const;
/// <summary>
/// Returns true if this URI has a non-null query string section.

View File

@ -36,7 +36,7 @@ relationship::relationship(
{
}
std::string relationship::id() const
const std::string& relationship::id() const
{
return id_;
}
@ -46,12 +46,12 @@ target_mode relationship::target_mode() const
return mode_;
}
uri relationship::source() const
const uri &relationship::source() const
{
return source_;
}
uri relationship::target() const
const uri &relationship::target() const
{
return target_;
}

View File

@ -16,7 +16,7 @@ std::string uri::to_string() const
return path_.string();
}
path uri::path() const
const path& uri::path() const
{
return path_;
}

View File

@ -28,16 +28,6 @@
#include <functional>
#include <set>
#include <detail/constants.hpp>
#include <detail/default_case.hpp>
#include <detail/implementations/cell_impl.hpp>
#include <detail/implementations/workbook_impl.hpp>
#include <detail/implementations/worksheet_impl.hpp>
#include <detail/serialization/excel_thumbnail.hpp>
#include <detail/serialization/vector_streambuf.hpp>
#include <detail/serialization/open_stream.hpp>
#include <detail/serialization/xlsx_consumer.hpp>
#include <detail/serialization/xlsx_producer.hpp>
#include <xlnt/cell/cell.hpp>
#include <xlnt/packaging/manifest.hpp>
#include <xlnt/packaging/relationship.hpp>
@ -61,12 +51,22 @@
#include <xlnt/worksheet/header_footer.hpp>
#include <xlnt/worksheet/range.hpp>
#include <xlnt/worksheet/worksheet.hpp>
#include <detail/constants.hpp>
#include <detail/default_case.hpp>
#include <detail/implementations/cell_impl.hpp>
#include <detail/implementations/workbook_impl.hpp>
#include <detail/implementations/worksheet_impl.hpp>
#include <detail/serialization/excel_thumbnail.hpp>
#include <detail/serialization/open_stream.hpp>
#include <detail/serialization/vector_streambuf.hpp>
#include <detail/serialization/xlsx_consumer.hpp>
#include <detail/serialization/xlsx_producer.hpp>
namespace {
using xlnt::detail::open_stream;
template<typename T>
template <typename T>
std::vector<T> keys(const std::vector<std::pair<T, xlnt::variant>> &container)
{
auto result = std::vector<T>();
@ -80,7 +80,7 @@ std::vector<T> keys(const std::vector<std::pair<T, xlnt::variant>> &container)
return result;
}
template<typename T>
template <typename T>
bool contains(const std::vector<std::pair<T, xlnt::variant>> &container, const T key)
{
for (const auto &iter : container)
@ -550,7 +550,7 @@ void workbook::register_package_part(relationship_type type)
void workbook::register_workbook_part(relationship_type type)
{
auto wb_rel = manifest().relationship(path("/"), relationship_type::office_document);
auto wb_path = manifest().canonicalize({ wb_rel });
auto wb_path = manifest().canonicalize({wb_rel});
if (!manifest().has_relationship(wb_path, type))
{
@ -738,22 +738,35 @@ worksheet workbook::create_sheet()
std::string title = "Sheet1";
int index = 1;
// make a unique sheet name. Sheet<1...n>
while (contains(title))
{
title = "Sheet" + std::to_string(++index);
}
// unique sheet id
size_t sheet_id = 1;
for (const auto ws : *this)
{
sheet_id = std::max(sheet_id, ws.id() + 1);
}
std::string sheet_filename = "sheet" + std::to_string(sheet_id) + ".xml";
d_->worksheets_.push_back(detail::worksheet_impl(this, sheet_id, title));
// unique sheet file name
auto workbook_rel = d_->manifest_.relationship(path("/"), relationship_type::office_document);
uri relative_sheet_uri(path("worksheets").append(sheet_filename).string());
auto workbook_files = d_->manifest_.relationships(workbook_rel.target().path());
auto rel_vec_contains = [&workbook_files](const xlnt::path &new_file_id) {
return workbook_files.end() != std::find_if(workbook_files.begin(), workbook_files.end(), [&new_file_id](const xlnt::relationship &rel) {
return rel.target().path() == new_file_id;
});
};
size_t file_id = sheet_id;
xlnt::path sheet_relative_path;
do
{
sheet_relative_path = path("worksheets").append("sheet" + std::to_string(file_id++) + ".xml");
} while (rel_vec_contains(sheet_relative_path));
uri relative_sheet_uri(sheet_relative_path.string());
auto absolute_sheet_path = path("/xl").append(relative_sheet_uri.path());
d_->manifest_.register_override_type(
absolute_sheet_path, "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml");
@ -1025,7 +1038,8 @@ void workbook::remove_sheet(worksheet ws)
for (auto &title_rel_id_pair : d_->sheet_title_rel_id_map_)
{
title_rel_id_pair.second = rel_id_map.count(title_rel_id_pair.second) > 0
? rel_id_map[title_rel_id_pair.second] : title_rel_id_pair.second;
? rel_id_map[title_rel_id_pair.second]
: title_rel_id_pair.second;
}
update_sheet_properties();
@ -1563,69 +1577,83 @@ void workbook::update_sheet_properties()
}
}
namespace {
// true if a sheet index is != worksheet relationship index
bool needs_reorder(const std::unordered_map<std::string, std::string> &title_to_rels,
const std::vector<std::string> &titles,
std::vector<std::string> &relation_ids)
{
bool all_match = true;
for (std::size_t title_index = 0; title_index < titles.size(); ++title_index)
{
const auto &rel = title_to_rels.at(titles[title_index]);
relation_ids.push_back(rel);
const auto expected_rel_id = "rId" + std::to_string(title_index + 1);
if (rel != expected_rel_id)
{
all_match = false;
}
}
return !all_match; // if all are as expected, reorder not required
};
struct rel_id_sorter
{
// true if lhs < rhs
bool operator()(const xlnt::relationship &lhs, const xlnt::relationship &rhs)
{
// format is rTd<decimal number 1..n>
if (lhs.id().size() < rhs.id().size()) // a number with more digits will be larger
{
return true;
}
return lhs.id() < rhs.id();
}
};
} // namespace
void workbook::reorder_relationships()
{
const auto wb_rel = manifest().relationship(path("/"), relationship_type::office_document);
const auto wb_path = wb_rel.target().path();
const auto relationships = manifest().relationships(wb_path);
std::unordered_map<std::string, relationship> rel_map;
const auto titles = sheet_titles();
const auto title_rel_id_map = d_->sheet_title_rel_id_map_;
bool needs_reorder = false;
for (const auto &rel : relationships)
{
rel_map[rel.id()] = rel;
if (rel.type() == relationship_type::worksheet)
{
for (auto title_index = std::size_t(0); title_index < titles.size(); ++title_index)
{
const auto title = titles[title_index];
if (title_rel_id_map.at(title) == rel.id())
{
const auto expected_rel_id = "rId" + std::to_string(title_index + 1);
if (expected_rel_id != rel.id())
{
needs_reorder = true;
}
break;
}
}
}
}
if (!needs_reorder)
// the relation ID corresponding to the title at the same index is copied into here
std::vector<std::string> worksheet_rel_ids;
worksheet_rel_ids.reserve(titles.size());
if (!needs_reorder(d_->sheet_title_rel_id_map_, titles, worksheet_rel_ids))
{
return;
}
for (const auto &rel : relationships)
// copy of existing relations
const auto wb_rel_target = manifest().relationship(path("/"), relationship_type::office_document).target();
auto rel_copy = manifest().relationships(wb_rel_target.path());
std::sort(rel_copy.begin(), rel_copy.end(), rel_id_sorter{});
// clear existing relations
for (const auto &rel : rel_copy)
{
manifest().unregister_relationship(uri(wb_path.string()), rel.id());
manifest().unregister_relationship(wb_rel_target, rel.id());
}
for (auto index = std::size_t(0); index < rel_map.size(); ++index)
// create new relations
std::size_t index = 0;
auto new_id = [&index]() { return "rId" + std::to_string(++index); }; // ids start from 1
// worksheets first
while (index < worksheet_rel_ids.size())
{
auto rel_id = "rId" + std::to_string(index + 1);
auto old_rel_id = std::string();
auto rel_it = std::find_if(rel_copy.begin(), rel_copy.end(),
[&](const relationship &rel) { return rel.id() == worksheet_rel_ids[index]; });
if (index < titles.size())
{
auto title = titles[index];
old_rel_id = title_rel_id_map.at(title);
d_->sheet_title_rel_id_map_[title] = rel_id;
} else {
old_rel_id = "rId" + std::to_string(index - titles.size() + 2);
std::string rel_id = new_id();
d_->sheet_title_rel_id_map_.at(titles[index - 1]) = rel_id; // update title -> relation mapping
manifest().register_relationship(relationship(rel_id, rel_it->type(),
rel_it->source(), rel_it->target(), rel_it->target_mode()));
}
auto old_rel = rel_map[old_rel_id];
auto new_rel = relationship(rel_id, old_rel.type(),
old_rel.source(), old_rel.target(), old_rel.target_mode());
manifest().register_relationship(new_rel);
// then all the other relations in the same order they started (just new indices)
for (const auto &old_rel : rel_copy)
{
if (old_rel.type() == relationship_type::worksheet)
{
continue;
}
manifest().register_relationship(relationship(new_id(), old_rel.type(),
old_rel.source(), old_rel.target(), old_rel.target_mode()));
}
}

Binary file not shown.

View File

@ -3,11 +3,11 @@
#include <sstream>
#include <unordered_set>
#include <xlnt/packaging/manifest.hpp>
#include <xlnt/workbook/workbook.hpp>
#include <detail/external/include_libstudxml.hpp>
#include <detail/serialization/vector_streambuf.hpp>
#include <detail/serialization/zstream.hpp>
#include <xlnt/packaging/manifest.hpp>
#include <xlnt/workbook/workbook.hpp>
class xml_helper
{
@ -63,8 +63,7 @@ public:
bool difference = false;
auto right_iter = right_parser.begin();
auto is_whitespace = [](const std::string &v)
{
auto is_whitespace = [](const std::string &v) {
return v.find_first_not_of("\n\r\t ") == std::string::npos;
};
@ -290,8 +289,7 @@ public:
if (std::abs(int(left_info.size()) - int(right_info.size())) == 1)
{
auto is_calc_chain = [](const xlnt::path &p)
{
auto is_calc_chain = [](const xlnt::path &p) {
return p.filename() == "calcChain.xml";
};
@ -306,7 +304,7 @@ public:
}
}
if (left_info.size() != right_info.size() && ! difference_is_missing_calc_chain)
if (left_info.size() != right_info.size() && !difference_is_missing_calc_chain)
{
std::cout << "left has a different number of files than right" << std::endl;
@ -338,6 +336,35 @@ public:
if (!compare_relationships(left_manifest, right_manifest))
{
std::cout << "relationship mismatch\n"
<< "Left:\n";
for (const auto &part : left_manifest.parts())
{
std::cout << "-part: " << part.string() << '\n';
auto rels = left_manifest.relationships(part);
for (auto &rel : rels)
{
std::cout << rel.id() << ':'
<< static_cast<int>(rel.type())
<< ':' << static_cast<int>(rel.target_mode())
<< ':' << rel.source().path().string()
<< ':' << rel.target().path().string() << '\n';
}
}
std::cout << "\nRight:\n";
for (const auto &part : right_manifest.parts())
{
std::cout << "-part: " << part.string() << '\n';
auto rels = right_manifest.relationships(part);
for (auto &rel : rels)
{
std::cout << rel.id()
<< ':' << static_cast<int>(rel.type())
<< ':' << static_cast<int>(rel.target_mode())
<< ':' << rel.source().path().string()
<< ':' << rel.target().path().string() << '\n';
}
}
return false;
}
@ -357,9 +384,11 @@ public:
}
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"
? "[Content_Types].xml" : right_manifest.content_type(left_member);
? "[Content_Types].xml"
: right_manifest.content_type(left_member);
if (left_content_type != right_content_type)
{

View File

@ -66,6 +66,7 @@ public:
register_test(test_comparison);
register_test(test_id_gen);
register_test(test_load_file);
register_test(test_Issue279);
}
void test_active_sheet()
@ -466,5 +467,29 @@ public:
wb_load5.load(data);
xlnt_assert_equals(wb_path, wb_load5);
}
void test_Issue279()
{
xlnt::workbook wb(path_helper::test_file("Issue279_workbook_delete_rename.xlsx"));
while (wb.sheet_count() > 1)
{
if (wb[1].title() != "BOM")
{
wb.remove_sheet(wb[1]);
}
else
{
wb.remove_sheet(wb[0]);
}
}
// get sheet bom change title
auto ws1 = wb.sheet_by_index(0);
ws1.title("checkedBom");
// report sheet
auto ws2 = wb.create_sheet(1);
ws2.title("REPORT");
//save a copy file
wb.save("temp.xlsx");
}
};
static workbook_test_suite x;