mirror of
https://github.com/tfussell/xlnt.git
synced 2024-03-22 13:11:17 +08:00
Merge pull request #310 from Crzyrndm/dev-reorder-wb-relations-PR
Resolves #279
This commit is contained in:
commit
f035b9041e
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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_;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ std::string uri::to_string() const
|
|||
return path_.string();
|
||||
}
|
||||
|
||||
path uri::path() const
|
||||
const path& uri::path() const
|
||||
{
|
||||
return path_;
|
||||
}
|
||||
|
|
|
@ -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,6 +51,16 @@
|
|||
#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 {
|
||||
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
BIN
tests/data/Issue279_workbook_delete_rename.xlsx
Normal file
BIN
tests/data/Issue279_workbook_delete_rename.xlsx
Normal file
Binary file not shown.
|
@ -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";
|
||||
};
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
Loading…
Reference in New Issue
Block a user