pull/686/merge
Liam 2022-12-03 11:55:06 -06:00 committed by GitHub
commit 98d698fb6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 536 additions and 19 deletions

View File

@ -82,6 +82,7 @@ struct stylesheet;
struct workbook_impl;
class xlsx_consumer;
class xlsx_producer;
struct defined_name;
} // namespace detail
@ -751,6 +752,38 @@ public:
/// </summary>
bool known_fonts_enabled() const;
// Defined Names
/// <summary>
/// Add a defined name to a sheet
/// </summary
void add_defined_name(detail::defined_name name);
/// <summary>
/// Returns a copy of workbook defined names
/// </summary>
std::vector<detail::defined_name> get_defined_names() const;
/// <summary>
/// Returns a reference to a single defined name
/// </summary>
detail::defined_name &get_defined_name(const std::size_t index);
/// <summary>
/// Returns a reference to a single defined name. Finds the first name that matches, there may be multiple with the same name
/// </summary>
detail::defined_name &get_defined_name(const std::string &name);
/// <summary>
/// Removes the defined name at the given index
/// </summary>
void remove_defined_name(const std::size_t index);
/// <summary>
/// Removes the defined name that first matches the passed name.
/// </summary>
void remove_defined_name(const std::string &name);
// Manifest
/// <summary>

View File

@ -64,7 +64,7 @@ namespace detail {
class xlsx_consumer;
class xlsx_producer;
struct defined_name;
struct worksheet_impl;
} // namespace detail
@ -729,6 +729,38 @@ public:
/// </summary>
cell_reference active_cell() const;
// Defined Names
/// <summary>
/// Add a defined name to a sheet
/// </summary
void add_defined_name(detail::defined_name name);
/// <summary>
/// Returns a copy of workbook defined names
/// </summary>
std::vector<detail::defined_name> get_defined_names() const;
/// <summary>
/// Returns a reference to a single defined name
/// </summary>
detail::defined_name &get_defined_name(const std::size_t index);
/// <summary>
/// Returns a reference to a single defined name. Finds the first name that matches, there may be multiple with the same name
/// </summary>
detail::defined_name &get_defined_name(const std::string &name);
/// <summary>
/// Removes the defined name at the given index
/// </summary>
void remove_defined_name(const std::size_t index);
/// <summary>
/// Removes the defined name that first matches the passed name.
/// </summary>
void remove_defined_name(const std::string &name);
// page breaks
/// <summary>

View File

@ -29,6 +29,7 @@
#include <detail/implementations/stylesheet.hpp>
#include <detail/implementations/worksheet_impl.hpp>
#include <detail/serialization/defined_name.hpp>
#include <xlnt/packaging/ext_list.hpp>
#include <xlnt/packaging/manifest.hpp>
#include <xlnt/utils/datetime.hpp>
@ -88,11 +89,20 @@ struct workbook_impl
extended_properties_ = other.extended_properties_;
custom_properties_ = other.custom_properties_;
defined_names_ = other.defined_names_;
return *this;
}
bool operator==(const workbook_impl &other)
bool operator==(const workbook_impl &other) const
{
if (defined_names_.size() != other.defined_names_.size()) return false;
for (std::size_t i = 0; i < defined_names_.size(); i++)
{
if (defined_names_[i] != other.defined_names_[i]) return false;
}
return active_sheet_index_ == other.active_sheet_index_
&& worksheets_ == other.worksheets_
&& shared_strings_ids_ == other.shared_strings_ids_
@ -168,6 +178,7 @@ struct workbook_impl
optional<std::string> abs_path_;
optional<std::size_t> arch_id_flags_;
optional<ext_list> extensions_;
std::vector<defined_name> defined_names_;
};
} // namespace detail

View File

@ -41,6 +41,7 @@
#include <xlnt/worksheet/print_options.hpp>
#include <xlnt/worksheet/sheet_pr.hpp>
#include <detail/implementations/cell_impl.hpp>
#include <detail/serialization/defined_name.hpp>
namespace xlnt {
@ -88,6 +89,8 @@ struct worksheet_impl
extension_list_ = other.extension_list_;
sheet_properties_ = other.sheet_properties_;
print_options_ = other.print_options_;
defined_names_ = other.defined_names_;
for (auto &cell : cell_map_)
{
@ -97,8 +100,15 @@ struct worksheet_impl
workbook *parent_;
bool operator==(const worksheet_impl& rhs) const
bool operator==(const worksheet_impl &rhs) const
{
if (defined_names_.size() != rhs.defined_names_.size()) return false;
for (std::size_t i = 0; i < defined_names_.size(); i++)
{
if (defined_names_[i] != rhs.defined_names_[i]) return false;
}
return id_ == rhs.id_
&& title_ == rhs.title_
&& format_properties_ == rhs.format_properties_
@ -122,6 +132,7 @@ struct worksheet_impl
&& print_options_ == rhs.print_options_
&& sheet_properties_ == rhs.sheet_properties_
&& extension_list_ == rhs.extension_list_;
}
std::size_t id_;
@ -160,6 +171,8 @@ struct worksheet_impl
std::string drawing_rel_id_;
optional<drawing::spreadsheet_drawing> drawing_;
std::vector<defined_name> defined_names_;
};
} // namespace detail

View File

@ -30,10 +30,63 @@ namespace detail {
struct defined_name
{
std::string name;
std::size_t sheet_id;
bool hidden;
std::string value;
defined_name &operator=(const defined_name &other)
{
name = other.name;
comment = other.comment;
custom_menu = other.custom_menu;
description = other.description;
help = other.help;
status_bar = other.status_bar;
sheet_id = other.sheet_id;
hidden = other.hidden;
function = other.function;
function_group_id = other.function_group_id;
shortcut_key = other.shortcut_key;
value = other.value;
return *this;
}
bool operator==(const defined_name &other) const
{
return name == other.name
&& comment == other.comment
&& custom_menu == other.custom_menu
&& description == other.description
&& help == other.help
&& status_bar == other.status_bar
&& sheet_id == other.sheet_id
&& hidden == other.hidden
&& function == other.function
&& function_group_id == other.function_group_id
&& shortcut_key == other.shortcut_key
&& value == other.value;
}
bool operator!=(const defined_name &other) const
{
return !(*this == other);
}
// Implements (most of) CT_RevisionDefinedName, there's several "old" members in the spec but they're also ignored in other librarie
std::string name; // A string representing the name for this defined name.
optional<std::string> comment; // A string representing a comment about the defined name.
optional<std::string> custom_menu; // A string representing the new custom menu text
optional<std::string> description; // A string representing the new description text for the defined name.
optional<std::string> help; // A string representing the new help topic text.
optional<std::string> status_bar; // A string representing the new status bar text.
optional<std::size_t> sheet_id; // An integer representing the id of the sheet to which this defined name belongs. This shall be used local defined names only. 0 indexed indexed.
optional<bool> hidden; // A Boolean value indicating whether the named range is now hidden.
optional<bool> function; // A Boolean value indicating that the defined name refers to a function. True if the defined name is a function, false otherwise.
optional<std::size_t> function_group_id;// Represents the new function group id.
optional<std::string> shortcut_key; // Represents the new keyboard shortcut. This is unsigned byte in the spec, but openpyxl uses string so let's try that
std::string value; // The actual value of the name, ie "='Sheet1'!A1"
};
} // namespace detail

View File

@ -534,7 +534,7 @@ std::string xlsx_consumer::read_worksheet_begin(const std::string &rel_id)
expect_start_element(qn("spreadsheetml", "worksheet"), xml::content::complex); // CT_Worksheet
skip_attributes({qn("mc", "Ignorable")});
read_defined_names(ws, defined_names_);
read_defined_names(ws, ws.d_->defined_names_);
while (in_element(qn("spreadsheetml", "worksheet")))
{
@ -2082,16 +2082,61 @@ void xlsx_consumer::read_office_document(const std::string &content_type) // CT_
defined_name name;
name.name = parser().attribute("name");
name.sheet_id = parser().attribute<std::size_t>("localSheetId");
name.hidden = false;
if (parser().attribute_present("comment"))
{
name.comment = parser().attribute<std::string>("comment");
}
if (parser().attribute_present("customMenu"))
{
name.custom_menu = parser().attribute<std::string>("customMenu");
}
if (parser().attribute_present("description"))
{
name.description = parser().attribute<std::string>("description");
}
if (parser().attribute_present("help"))
{
name.help = parser().attribute<std::string>("help");
}
if (parser().attribute_present("statusBar"))
{
name.status_bar = parser().attribute<std::string>("statusBar");
}
if (parser().attribute_present("localSheetId"))
{
name.sheet_id = parser().attribute<std::size_t>("localSheetId");
}
if (parser().attribute_present("hidden"))
{
name.hidden = is_true(parser().attribute("hidden"));
}
if (parser().attribute_present("function"))
{
name.function = is_true(parser().attribute("function"));
}
if (parser().attribute_present("functionGroupId"))
{
name.function_group_id = parser().attribute<std::size_t>("functionGroupId");
}
if (parser().attribute_present("shortcutKey"))
{
name.shortcut_key = parser().attribute<std::string>("shortcutKey");
}
parser().attribute_map(); // skip remaining attributes
name.value = read_text();
defined_names_.push_back(name);
target_.d_->defined_names_.push_back(name);
expect_end_element(qn("spreadsheetml", "definedName"));
}
}
@ -2195,6 +2240,7 @@ void xlsx_consumer::read_office_document(const std::string &content_type) // CT_
}
}
std::vector<defined_name> workbook_names;
for (auto worksheet_rel : manifest().relationships(workbook_path, relationship_type::worksheet))
{
auto title = std::find_if(target_.d_->sheet_title_rel_id_map_.begin(),
@ -2203,7 +2249,7 @@ void xlsx_consumer::read_office_document(const std::string &content_type) // CT_
return p.second == worksheet_rel.id();
})->first;
auto id = sheet_title_id_map_[title];
auto id = sheet_title_id_map_[title]; // 1-indexed
auto index = sheet_title_index_map_[title];
auto insertion_iter = target_.d_->worksheets_.begin();
@ -2215,11 +2261,34 @@ void xlsx_consumer::read_office_document(const std::string &content_type) // CT_
current_worksheet_ = &*target_.d_->worksheets_.emplace(insertion_iter, &target_, id, title);
// If there's any defined names that are worksheet specific, move them here.
for (std::size_t i = 0; i < target_.d_->defined_names_.size(); i++)
{
const auto &name = target_.d_->defined_names_[i];
if (name.sheet_id.is_set())
{
const auto target_id = name.sheet_id.get();
if (target_id == index)
{
// It's a match, remove it from the workbook and add it to the sheet
current_worksheet_->defined_names_.push_back(name);
}
}
else
{
// Name is global and belongs to the workbook, if i'ts not already added
if (std::find(workbook_names.begin(), workbook_names.end(), name) == workbook_names.end())
workbook_names.push_back(name);
}
}
if (!streaming_)
{
read_part({workbook_rel, worksheet_rel});
}
}
// Update the workbook with the new defined names
target_.d_->defined_names_ = workbook_names;
}
// Write Workbook Relationship Target Parts

View File

@ -435,6 +435,9 @@ void xlsx_producer::write_workbook(const relationship &rel)
std::size_t num_visible = 0;
std::vector<defined_name> defined_names;
defined_names = source_.d_->defined_names_;
std::size_t sheet_id = 1;
for (auto ws : source_)
{
if (!ws.has_page_setup() || ws.page_setup().sheet_state() == sheet_state::visible)
@ -444,30 +447,35 @@ void xlsx_producer::write_workbook(const relationship &rel)
auto title_ref = "'" + ws.title() + "'!";
bool added_auto_filter = false;
bool added_print_area = false;
bool added_print_titles = false;
if (ws.has_auto_filter())
{
defined_name name;
name.sheet_id = ws.id();
name.sheet_id = sheet_id;
name.name = "_xlnm._FilterDatabase";
name.hidden = true;
name.value = title_ref + range_reference::make_absolute(ws.auto_filter()).to_string();
defined_names.push_back(name);
added_auto_filter = true;
}
if (ws.has_print_area())
{
defined_name name;
name.sheet_id = ws.id();
name.sheet_id = sheet_id;
name.name = "_xlnm.Print_Area";
name.hidden = false;
name.value = title_ref + range_reference::make_absolute(ws.print_area()).to_string();
defined_names.push_back(name);
added_print_area = true;
}
if (ws.has_print_titles())
{
defined_name name;
name.sheet_id = ws.id();
name.sheet_id = sheet_id;
name.name = "_xlnm.Print_Titles";
name.hidden = false;
@ -489,7 +497,22 @@ void xlsx_producer::write_workbook(const relationship &rel)
}
defined_names.push_back(name);
added_print_titles = true;
}
// Add any sheet defined names to the vector
for (auto &sheet_defined_name : ws.d_->defined_names_)
{
sheet_defined_name.sheet_id = sheet_id;
if (sheet_defined_name.name == "_xlnm._FilterDatabase" && !added_auto_filter)
defined_names.push_back(sheet_defined_name);
else if (sheet_defined_name.name == "_xlnm.Print_Area" && !added_print_area)
defined_names.push_back(sheet_defined_name);
else if (sheet_defined_name.name == "_xlnm.Print_Titles" && !added_print_titles)
defined_names.push_back(sheet_defined_name);
else if (!added_auto_filter && !added_print_area && !added_print_titles)
defined_names.push_back(sheet_defined_name);
}
sheet_id++;
}
if (num_visible == 0)
@ -664,11 +687,66 @@ void xlsx_producer::write_workbook(const relationship &rel)
{
write_start_element(xmlns, "definedName");
write_attribute("name", name.name);
if (name.hidden)
if (name.comment.is_set())
{
write_attribute("hidden", write_bool(true));
write_attribute("comment", name.comment.get());
}
write_attribute("localSheetId", std::to_string(name.sheet_id - 1)); // 0-indexed for some reason
if (name.custom_menu.is_set())
{
write_attribute("customMenu", name.custom_menu.get());
}
if (name.description.is_set())
{
write_attribute("description", name.description.get());
}
if (name.help.is_set())
{
write_attribute("help", name.help.get());
}
if (name.status_bar.is_set())
{
write_attribute("statusBar", name.status_bar.get());
}
if (name.sheet_id.is_set())
{
write_attribute("localSheetId", std::to_string(name.sheet_id.get() - 1)); // Don't think this is meant to require subtracting 1?
}
if (name.hidden.is_set())
{
const auto hidden_state = name.hidden.get();
if (hidden_state)
{
write_attribute("hidden", write_bool(true));
}
}
if (name.function.is_set())
{
const auto function_state = name.function.get();
if (function_state)
{
write_attribute("function", write_bool(true));
}
}
if (name.function_group_id.is_set())
{
const auto function_group_id = name.function_group_id.get();
write_attribute("functionGroupId", std::to_string(function_group_id));
}
if (name.shortcut_key.is_set())
{
write_attribute("shortcutKey", name.shortcut_key.get());
}
write_characters(name.value);
write_end_element(xmlns, "definedName");
}

View File

@ -60,6 +60,7 @@
#include <detail/serialization/open_stream.hpp>
#include <detail/serialization/vector_streambuf.hpp>
#include <detail/serialization/xlsx_consumer.hpp>
#include <detail/serialization/defined_name.hpp>
#include <detail/serialization/xlsx_producer.hpp>
namespace {
@ -1713,4 +1714,50 @@ void workbook::reorder_relationships()
}
}
void workbook::add_defined_name(detail::defined_name name)
{
d_->defined_names_.push_back(name);
}
std::vector<detail::defined_name> workbook::get_defined_names() const
{
return d_->defined_names_;
}
detail::defined_name &workbook::get_defined_name(const std::size_t index)
{
return d_->defined_names_[index];
}
detail::defined_name &workbook::get_defined_name(const std::string &name)
{
// Only return the first matching
for (auto &defined_name : d_->defined_names_)
if (defined_name.name == name)
return defined_name;
throw key_not_found();
}
void workbook::remove_defined_name(const std::size_t index)
{
d_->defined_names_.erase(d_->defined_names_.begin() + index);
}
void workbook::remove_defined_name(const std::string &name)
{
// Only remove first matching
std::size_t offending_index = 0;
std::size_t index = 0;
for (auto &defined_name : d_->defined_names_)
{
if (defined_name.name == name)
{
offending_index = index;
break;
}
index++;
}
if (d_->defined_names_.size() > 0)
remove_defined_name(offending_index);
}
} // namespace xlnt

View File

@ -1352,4 +1352,51 @@ bool worksheet::is_empty() const
return d_->cell_map_.empty();
}
void worksheet::add_defined_name(detail::defined_name name)
{
d_->defined_names_.push_back(name);
}
std::vector<detail::defined_name> worksheet::get_defined_names() const
{
return d_->defined_names_;
}
detail::defined_name &worksheet::get_defined_name(const std::size_t index)
{
return d_->defined_names_[index];
}
detail::defined_name &worksheet::get_defined_name(const std::string &name)
{
// Only return the first matching
for (auto &defined_name : d_->defined_names_)
if (defined_name.name == name)
return defined_name;
throw key_not_found();
}
void worksheet::remove_defined_name(const std::size_t index)
{
d_->defined_names_.erase(d_->defined_names_.begin() + index);
}
void worksheet::remove_defined_name(const std::string &name)
{
// Only remove first matching
std::size_t offending_index = 0;
std::size_t index = 0;
for (auto &defined_name : d_->defined_names_)
{
if (defined_name.name == name)
{
offending_index = index;
break;
}
index++;
}
if (d_->defined_names_.size() > 0)
remove_defined_name(offending_index);
}
} // namespace xlnt

Binary file not shown.

View File

@ -73,6 +73,7 @@ public:
register_test(test_Issue503_external_link_load);
register_test(test_formatting);
register_test(test_active_sheet);
register_test(test_named_range);
}
bool workbook_matches_file(xlnt::workbook &wb, const xlnt::path &file)
@ -808,6 +809,11 @@ public:
wb.load(path_helper::test_file("20_active_sheet.xlsx"));
xlnt_assert_equals(wb.active_sheet(), wb[2]);
}
void test_named_range()
{
xlnt_assert(round_trip_matches_rw(path_helper::test_file("19_defined_names.xlsx")));
}
};
static serialization_test_suite x;

View File

@ -36,6 +36,7 @@
#include <xlnt/workbook/worksheet_iterator.hpp>
#include <xlnt/worksheet/range.hpp>
#include <xlnt/worksheet/worksheet.hpp>
#include <detail/serialization/defined_name.hpp>
class workbook_test_suite : public test_suite
{
@ -60,6 +61,9 @@ public:
register_test(test_add_named_range);
register_test(test_get_named_range);
register_test(test_remove_named_range);
register_test(test_add_defined_name);
register_test(test_modify_defined_name);
register_test(test_remove_defined_name);
register_test(test_post_increment_iterator);
register_test(test_copy_iterator);
register_test(test_manifest);
@ -342,6 +346,63 @@ public:
xlnt_assert_throws(wb.remove_named_range("test_nr2"), std::runtime_error);
}
void test_add_defined_name()
{
xlnt::workbook wb;
auto new_sheet = wb.create_sheet();
xlnt::detail::defined_name name;
const std::string dname = "Workbook Defined Name";
const std::string dcomment = "This should only exist on a workbook";
name.name = dname;
name.comment = dcomment;
name.value = "='Sheet1'!A1";
wb.add_defined_name(name);
xlnt_assert(wb.get_defined_names().size() == 1);
}
void test_modify_defined_name()
{
xlnt::workbook wb;
auto new_sheet = wb.create_sheet();
xlnt::detail::defined_name name;
const std::string dname = "Workbook Defined Name";
const std::string dcomment = "This should only exist on a workbook";
name.name = dname;
name.comment = dcomment;
name.value = "='Sheet1'!A1";
wb.add_defined_name(name);
xlnt_assert(wb.get_defined_name(0).name == dname);
xlnt_assert(wb.get_defined_name(dname).comment == dcomment);
wb.get_defined_name(0).hidden = true;
xlnt_assert(wb.get_defined_name(0).hidden == true);
xlnt_assert_throws(wb.get_defined_name("Doesn't exist"), xlnt::key_not_found);
}
void test_remove_defined_name()
{
xlnt::workbook wb;
auto new_sheet = wb.create_sheet();
xlnt::detail::defined_name name;
const std::string dname = "Workbook Defined Name";
const std::string dcomment = "This should only exist on a workbook";
name.name = dname;
name.comment = dcomment;
name.value = "='Sheet1'!A1";
wb.add_defined_name(name);
xlnt_assert(wb.get_defined_names().size() == 1);
wb.remove_defined_name(dname);
xlnt_assert(wb.get_defined_names().size() == 0);
}
void test_post_increment_iterator()
{
xlnt::workbook wb;

View File

@ -29,6 +29,7 @@
#include <xlnt/worksheet/range.hpp>
#include <xlnt/worksheet/row_properties.hpp>
#include <xlnt/worksheet/worksheet.hpp>
#include <detail/serialization/defined_name.hpp>
#include <helpers/test_suite.hpp>
class worksheet_test_suite : public test_suite
@ -111,6 +112,9 @@ public:
register_test(test_hidden_sheet);
register_test(test_xlsm_read_write);
register_test(test_issue_484);
register_test(test_add_defined_name);
register_test(test_modify_defined_name);
register_test(test_remove_defined_name);
}
void test_new_worksheet()
@ -1674,6 +1678,69 @@ public:
xlnt_assert_equals("B12:B12", ws.columns(true).reference());
xlnt_assert_equals("A1:B12", ws.columns(false).reference());
}
void test_add_defined_name()
{
xlnt::workbook wb;
auto ws = wb.create_sheet();
xlnt::detail::defined_name name;
const std::string dname = "Worksheet Defined Name";
const std::string dcomment = "This should only exist on a Worksheet";
name.name = dname;
name.comment = dcomment;
name.value = "='Sheet1'!A1";
ws.add_defined_name(name);
xlnt_assert(ws.get_defined_names().size() == 1);
ws = wb.create_sheet();
xlnt_assert(ws.get_defined_names().size() == 0);
}
void test_modify_defined_name()
{
xlnt::workbook wb;
auto ws = wb.create_sheet();
xlnt::detail::defined_name name;
const std::string dname = "Worksheet Defined Name";
const std::string dcomment = "This should only exist on a Worksheet";
name.name = dname;
name.comment = dcomment;
name.value = "='Sheet1'!A1";
ws.add_defined_name(name);
xlnt_assert(ws.get_defined_name(0).name == dname);
xlnt_assert(ws.get_defined_name(dname).comment == dcomment);
ws.get_defined_name(0).hidden = true;
xlnt_assert(ws.get_defined_name(0).hidden == true);
xlnt_assert_throws(ws.get_defined_name("Doesn't exist"), xlnt::key_not_found);
ws = wb.create_sheet();
xlnt_assert_throws(ws.get_defined_name(dname), xlnt::key_not_found);
}
void test_remove_defined_name()
{
xlnt::workbook wb;
auto ws = wb.create_sheet();
xlnt::detail::defined_name name;
const std::string dname = "Worksheet Defined Name";
const std::string dcomment = "This should only exist on a Worksheet";
name.name = dname;
name.comment = dcomment;
name.value = "='Sheet1'!A1";
ws.add_defined_name(name);
xlnt_assert(ws.get_defined_names().size() == 1);
ws.remove_defined_name(dname);
xlnt_assert(ws.get_defined_names().size() == 0);
}
};
static worksheet_test_suite x;