diff --git a/include/xlnt/worksheet/worksheet.hpp b/include/xlnt/worksheet/worksheet.hpp
index 856c1a41..a4cf9e07 100644
--- a/include/xlnt/worksheet/worksheet.hpp
+++ b/include/xlnt/worksheet/worksheet.hpp
@@ -652,37 +652,52 @@ public:
///
/// Sets rows to repeat at top during printing.
///
- void print_title_rows(row_t first_row, row_t last_row);
+ void print_title_rows(row_t start, row_t end);
///
- /// Sets rows to repeat at top during printing.
+ /// Get rows to repeat at top during printing.
///
- void print_title_rows(row_t last_row);
+ optional> print_title_rows() const;
///
/// Sets columns to repeat at left during printing.
///
- void print_title_cols(column_t first_column, column_t last_column);
+ void print_title_cols(column_t start, column_t end);
+
+ ///
+ /// Get columns to repeat at left during printing.
+ ///
+ optional> print_title_cols() const;
///
- /// Sets columns to repeat at left during printing.
+ /// Returns true if the sheet has print titles defined.
///
- void print_title_cols(column_t last_column);
+ bool has_print_titles() const;
///
- /// Returns a string representation of the defined print titles.
+ /// Remove all print titles.
///
- std::string print_titles() const;
+ void clear_print_titles();
///
/// Sets the print area of this sheet to print_area.
///
void print_area(const std::string &print_area);
+ ///
+ /// Clear the print area of this sheet.
+ ///
+ void clear_print_area();
+
///
/// Returns the print area defined for this sheet.
///
range_reference print_area() const;
+
+ ///
+ /// Returns true if the print area is defined for this sheet.
+ ///
+ bool has_print_area() const;
///
/// Returns true if this sheet has any number of views defined.
diff --git a/source/detail/implementations/worksheet_impl.hpp b/source/detail/implementations/worksheet_impl.hpp
index 8c776a32..7afcfb1d 100644
--- a/source/detail/implementations/worksheet_impl.hpp
+++ b/source/detail/implementations/worksheet_impl.hpp
@@ -143,9 +143,8 @@ struct worksheet_impl
optional phonetic_properties_;
optional header_footer_;
- std::string print_title_cols_;
- std::string print_title_rows_;
-
+ optional> print_title_cols_;
+ optional> print_title_rows_;
optional print_area_;
std::vector views_;
diff --git a/source/detail/serialization/defined_name.hpp b/source/detail/serialization/defined_name.hpp
new file mode 100644
index 00000000..3aeb9132
--- /dev/null
+++ b/source/detail/serialization/defined_name.hpp
@@ -0,0 +1,40 @@
+// Copyright (c) 2014-2021 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, ARISING 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
+
+#pragma once
+
+#include
+
+namespace xlnt {
+namespace detail {
+
+struct defined_name
+{
+ std::string name;
+ std::size_t sheet_id;
+ bool hidden;
+ std::string value;
+};
+
+} // namespace detail
+} // namespace xlnt
diff --git a/source/detail/serialization/xlsx_consumer.cpp b/source/detail/serialization/xlsx_consumer.cpp
index 7959372a..88272872 100644
--- a/source/detail/serialization/xlsx_consumer.cpp
+++ b/source/detail/serialization/xlsx_consumer.cpp
@@ -41,6 +41,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -448,6 +449,70 @@ void xlsx_consumer::read_worksheet(const std::string &rel_id)
}
}
+void read_defined_names(worksheet ws, std::vector defined_names)
+{
+ for (auto &name : defined_names)
+ {
+ if (name.sheet_id != ws.id() - 1)
+ {
+ continue;
+ }
+
+ if (name.name == "_xlnm.Print_Titles")
+ {
+ // Basic print titles parser
+ // A print title definition looks like "'Sheet3'!$B:$E,'Sheet3'!$2:$4"
+ // There are three cases: columns only, rows only, and both (separated by a comma).
+ // For this reason, we loop up to two times parsing each component.
+ // Titles may be quoted (with single quotes) or unquoted. We ignore them for now anyways.
+ // References are always absolute.
+ // Move this into a separate function if it needs to be used in other places.
+ auto i = std::size_t(0);
+ for (auto count = 0; count < 2; count++)
+ {
+ // Split into components based on "!", ":", and "," characters
+ auto j = i;
+ i = name.value.find("!", j);
+ auto title = name.value.substr(j, i - j);
+ j = i + 2; // skip "!$"
+ i = name.value.find(":", j);
+ auto from = name.value.substr(j, i - j);
+ j = i + 2; // skip ":$"
+ i = name.value.find(",", j);
+ auto to = name.value.substr(j, i - j);
+
+ // Apply to the worksheet
+ if (isalpha(from.front())) // alpha=>columns
+ {
+ ws.print_title_cols(from, to);
+ }
+ else // numeric=>rows
+ {
+ ws.print_title_rows(std::stoul(from), std::stoul(to));
+ }
+
+ // Check for end condition
+ if (i == std::string::npos)
+ {
+ break;
+ }
+
+ i++; // skip "," for next iteration
+ }
+ }
+ else if (name.name == "_xlnm._FilterDatabase")
+ {
+ auto i = name.value.find("!");
+ ws.auto_filter(name.value.substr(i + 1));
+ }
+ else if (name.name == "_xlnm.Print_Area")
+ {
+ auto i = name.value.find("!");
+ ws.print_area(name.value.substr(i + 1));
+ }
+ }
+}
+
std::string xlsx_consumer::read_worksheet_begin(const std::string &rel_id)
{
if (streaming_ && streaming_cell_ == nullptr)
@@ -468,6 +533,8 @@ 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_);
while (in_element(qn("spreadsheetml", "worksheet")))
{
@@ -2015,7 +2082,24 @@ void xlsx_consumer::read_office_document(const std::string &content_type) // CT_
}
else if (current_workbook_element == qn("workbook", "definedNames")) // CT_DefinedNames 0-1
{
- skip_remaining_content(current_workbook_element);
+ while (in_element(qn("workbook", "definedNames")))
+ {
+ expect_start_element(qn("spreadsheetml", "definedName"), xml::content::mixed);
+
+ defined_name name;
+ name.name = parser().attribute("name");
+ name.sheet_id = parser().attribute("localSheetId");
+ name.hidden = false;
+ if (parser().attribute_present("hidden"))
+ {
+ name.hidden = is_true(parser().attribute("hidden"));
+ }
+ parser().attribute_map(); // skip remaining attributes
+ name.value = read_text();
+ defined_names_.push_back(name);
+
+ expect_end_element(qn("spreadsheetml", "definedName"));
+ }
}
else if (current_workbook_element == qn("workbook", "calcPr")) // CT_CalcPr 0-1
{
@@ -2123,14 +2207,14 @@ void xlsx_consumer::read_office_document(const std::string &content_type) // CT_
target_.d_->sheet_title_rel_id_map_.end(),
[&](const std::pair &p) {
return p.second == worksheet_rel.id();
- })
- ->first;
+ })->first;
auto id = sheet_title_id_map_[title];
auto index = sheet_title_index_map_[title];
auto insertion_iter = target_.d_->worksheets_.begin();
- while (insertion_iter != target_.d_->worksheets_.end() && sheet_title_index_map_[insertion_iter->title_] < index)
+ while (insertion_iter != target_.d_->worksheets_.end()
+ && sheet_title_index_map_[insertion_iter->title_] < index)
{
++insertion_iter;
}
diff --git a/source/detail/serialization/xlsx_consumer.hpp b/source/detail/serialization/xlsx_consumer.hpp
index 914b76e9..182c9b36 100644
--- a/source/detail/serialization/xlsx_consumer.hpp
+++ b/source/detail/serialization/xlsx_consumer.hpp
@@ -56,6 +56,7 @@ namespace detail {
class izstream;
struct cell_impl;
+struct defined_name;
struct worksheet_impl;
///
@@ -424,6 +425,8 @@ private:
detail::worksheet_impl *current_worksheet_;
number_serialiser converter_;
+
+ std::vector defined_names_;
};
} // namespace detail
diff --git a/source/detail/serialization/xlsx_producer.cpp b/source/detail/serialization/xlsx_producer.cpp
index 82df530a..6f6e26cc 100644
--- a/source/detail/serialization/xlsx_producer.cpp
+++ b/source/detail/serialization/xlsx_producer.cpp
@@ -41,6 +41,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -432,7 +433,7 @@ void xlsx_producer::write_custom_properties(const relationship & /*rel*/)
void xlsx_producer::write_workbook(const relationship &rel)
{
std::size_t num_visible = 0;
- bool any_defined_names = false;
+ std::vector defined_names;
for (auto ws : source_)
{
@@ -440,10 +441,54 @@ void xlsx_producer::write_workbook(const relationship &rel)
{
num_visible++;
}
-
+
+ auto title_ref = "'" + ws.title() + "'!";
+
if (ws.has_auto_filter())
{
- any_defined_names = true;
+ defined_name name;
+ name.sheet_id = ws.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);
+ }
+
+ if (ws.has_print_area())
+ {
+ defined_name name;
+ name.sheet_id = ws.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);
+ }
+
+ if (ws.has_print_titles())
+ {
+ defined_name name;
+ name.sheet_id = ws.id();
+ name.name = "_xlnm.Print_Titles";
+ name.hidden = false;
+
+ auto cols = ws.print_title_cols();
+ if (cols.is_set())
+ {
+ name.value = title_ref + "$" + cols.get().first.column_string()
+ + ":" + "$" + cols.get().second.column_string();
+ }
+ auto rows = ws.print_title_rows();
+ if (rows.is_set())
+ {
+ if (!name.value.empty())
+ {
+ name.value.push_back(',');
+ }
+ name.value += title_ref + "$" + std::to_string(rows.get().first)
+ + ":" + "$" + std::to_string(rows.get().second);
+ }
+
+ defined_names.push_back(name);
}
}
@@ -612,16 +657,21 @@ void xlsx_producer::write_workbook(const relationship &rel)
write_end_element(xmlns, "sheets");
- if (any_defined_names)
+ if (!defined_names.empty())
{
write_start_element(xmlns, "definedNames");
- /*
- write_attribute("name", "_xlnm._FilterDatabase");
- write_attribute("hidden", write_bool(true));
- write_attribute("localSheetId", "0");
- write_characters("'" + ws.title() + "'!" +
- range_reference::make_absolute(ws.auto_filter()).to_string());
- */
+ for (auto name : defined_names)
+ {
+ write_start_element(xmlns, "definedName");
+ write_attribute("name", name.name);
+ if (name.hidden)
+ {
+ write_attribute("hidden", write_bool(true));
+ }
+ write_attribute("localSheetId", std::to_string(name.sheet_id - 1)); // 0-indexed for some reason
+ write_characters(name.value);
+ write_end_element(xmlns, "definedName");
+ }
write_end_element(xmlns, "definedNames");
}
diff --git a/source/worksheet/worksheet.cpp b/source/worksheet/worksheet.cpp
index d7e7d9ef..bd64ac37 100644
--- a/source/worksheet/worksheet.cpp
+++ b/source/worksheet/worksheet.cpp
@@ -1146,40 +1146,35 @@ worksheet::const_iterator worksheet::end() const
return cend();
}
-void worksheet::print_title_rows(row_t last_row)
+void worksheet::print_title_rows(row_t start, row_t end)
{
- print_title_rows(1, last_row);
+ d_->print_title_rows_ = std::make_pair(start, end);
}
-void worksheet::print_title_rows(row_t first_row, row_t last_row)
+optional> worksheet::print_title_rows() const
{
- d_->print_title_rows_ = std::to_string(first_row) + ":" + std::to_string(last_row);
+ return d_->print_title_rows_;
}
-void worksheet::print_title_cols(column_t last_column)
+void worksheet::print_title_cols(column_t start, column_t end)
{
- print_title_cols(1, last_column);
+ d_->print_title_cols_ = std::make_pair(start, end);
}
-void worksheet::print_title_cols(column_t first_column, column_t last_column)
+optional> worksheet::print_title_cols() const
{
- d_->print_title_cols_ = first_column.column_string() + ":" + last_column.column_string();
+ return d_->print_title_cols_;
}
-std::string worksheet::print_titles() const
+bool worksheet::has_print_titles() const
{
- if (!d_->print_title_rows_.empty() && !d_->print_title_cols_.empty())
- {
- return d_->title_ + "!" + d_->print_title_rows_ + "," + d_->title_ + "!" + d_->print_title_cols_;
- }
- else if (!d_->print_title_cols_.empty())
- {
- return d_->title_ + "!" + d_->print_title_cols_;
- }
- else
- {
- return d_->title_ + "!" + d_->print_title_rows_;
- }
+ return d_->print_title_cols_.is_set() || d_->print_title_rows_.is_set();
+}
+
+void worksheet::clear_print_titles()
+{
+ d_->print_title_rows_.clear();
+ d_->print_title_cols_.clear();
}
void worksheet::print_area(const std::string &print_area)
@@ -1192,6 +1187,16 @@ range_reference worksheet::print_area() const
return d_->print_area_.get();
}
+bool worksheet::has_print_area() const
+{
+ return d_->print_area_.is_set();
+}
+
+void worksheet::clear_print_area()
+{
+ return d_->print_area_.clear();
+}
+
bool worksheet::has_view() const
{
return !d_->views_.empty();
diff --git a/tests/data/19_defined_names.xlsx b/tests/data/19_defined_names.xlsx
new file mode 100644
index 00000000..4c430ba3
Binary files /dev/null and b/tests/data/19_defined_names.xlsx differ
diff --git a/tests/utils/datetime_test_suite.cpp b/tests/utils/datetime_test_suite.cpp
index 1007b918..36c6649e 100644
--- a/tests/utils/datetime_test_suite.cpp
+++ b/tests/utils/datetime_test_suite.cpp
@@ -117,9 +117,11 @@ public:
void test_weekday()
{
- xlnt_assert_equals(xlnt::date(2000, 1, 1).weekday(), 6);
- xlnt_assert_equals(xlnt::date(2016, 7, 15).weekday(), 5);
- xlnt_assert_equals(xlnt::date(2018, 10, 29).weekday(), 1);
+ xlnt_assert_equals(xlnt::date(2000, 1, 1).weekday(), 6); // January 1st 2000 was a Saturday
+ xlnt_assert_equals(xlnt::date(2016, 7, 15).weekday(), 5); // July 15th 2016 was a Friday
+ xlnt_assert_equals(xlnt::date(2018, 10, 29).weekday(), 1); // October 29th 2018 was Monday
+ xlnt_assert_equals(xlnt::date(1970, 1, 1).weekday(), 4); // January 1st 1970 was a Thursday
}
};
+
static datetime_test_suite x;
diff --git a/tests/worksheet/worksheet_test_suite.cpp b/tests/worksheet/worksheet_test_suite.cpp
index 1ca50caa..9c1097a0 100644
--- a/tests/worksheet/worksheet_test_suite.cpp
+++ b/tests/worksheet/worksheet_test_suite.cpp
@@ -52,7 +52,6 @@ public:
register_test(test_no_cols);
register_test(test_one_cell);
register_test(test_cols);
- register_test(test_auto_filter);
register_test(test_getitem);
register_test(test_setitem);
register_test(test_getslice);
@@ -62,9 +61,7 @@ public:
register_test(test_merge_range_string);
register_test(test_unmerge_bad);
register_test(test_unmerge_range_string);
- register_test(test_print_titles_old);
- register_test(test_print_titles_new);
- register_test(test_print_area);
+ register_test(test_defined_names);
register_test(test_freeze_panes_horiz);
register_test(test_freeze_panes_vert);
register_test(test_freeze_panes_both);
@@ -303,21 +300,6 @@ public:
xlnt_assert_equals(cols[2][8].value(), "last");
}
- void test_auto_filter()
- {
- xlnt::workbook wb;
- auto ws = wb.active_sheet();
-
- ws.auto_filter(ws.range("a1:f1"));
- xlnt_assert_equals(ws.auto_filter(), "A1:F1");
-
- ws.clear_auto_filter();
- xlnt_assert(!ws.has_auto_filter());
-
- ws.auto_filter("c1:g9");
- xlnt_assert_equals(ws.auto_filter(), "C1:G9");
- }
-
void test_getitem()
{
xlnt::workbook wb;
@@ -416,43 +398,53 @@ public:
xlnt_assert_equals(ws.merged_ranges().size(), 0);
}
- void test_print_titles_old()
+ void test_defined_names()
{
xlnt::workbook wb;
+ wb.load(path_helper::test_file("19_defined_names.xlsx"));
+
+ auto ws1 = wb.sheet_by_index(0);
- auto ws = wb.active_sheet();
- ws.print_title_rows(3);
- xlnt_assert_equals(ws.print_titles(), "Sheet1!1:3");
+ xlnt_assert(!ws1.has_print_area());
- auto ws2 = wb.create_sheet();
- ws2.print_title_cols(4);
- xlnt_assert_equals(ws2.print_titles(), "Sheet2!A:D");
- }
+ xlnt_assert(ws1.has_auto_filter());
+ xlnt_assert_equals(ws1.auto_filter().to_string(), "A1:A6");
- void test_print_titles_new()
- {
- xlnt::workbook wb;
+ xlnt_assert(ws1.has_print_titles());
+ xlnt_assert(!ws1.print_title_cols().is_set());
+ xlnt_assert(ws1.print_title_rows().is_set());
+ xlnt_assert_equals(ws1.print_title_rows().get().first, 1);
+ xlnt_assert_equals(ws1.print_title_rows().get().second, 3);
+
+ auto ws2 = wb.sheet_by_index(1);
- auto ws = wb.active_sheet();
- ws.print_title_rows(4);
- xlnt_assert_equals(ws.print_titles(), "Sheet1!1:4");
+ xlnt_assert(ws2.has_print_area());
+ xlnt_assert_equals(ws2.print_area().to_string(), "$B$4:$B$4");
- auto ws2 = wb.create_sheet();
- ws2.print_title_cols("F");
- xlnt_assert_equals(ws2.print_titles(), "Sheet2!A:F");
+ xlnt_assert(!ws2.has_auto_filter());
- auto ws3 = wb.create_sheet();
- ws3.print_title_rows(2, 3);
- ws3.print_title_cols("C", "D");
- xlnt_assert_equals(ws3.print_titles(), "Sheet3!2:3,Sheet3!C:D");
- }
+ xlnt_assert(ws2.has_print_titles());
+ xlnt_assert(!ws2.print_title_rows().is_set());
+ xlnt_assert(ws2.print_title_cols().is_set());
+ xlnt_assert_equals(ws2.print_title_cols().get().first, "A");
+ xlnt_assert_equals(ws2.print_title_cols().get().second, "D");
+
+ auto ws3 = wb.sheet_by_index(2);
- void test_print_area()
- {
- xlnt::workbook wb;
- auto ws = wb.active_sheet();
- ws.print_area("A1:F5");
- xlnt_assert_equals(ws.print_area(), "$A$1:$F$5");
+ xlnt_assert(ws3.has_print_area());
+ xlnt_assert_equals(ws3.print_area().to_string(), "$B$2:$E$4");
+
+ xlnt_assert(!ws3.has_auto_filter());
+
+ xlnt_assert(ws3.has_print_titles());
+ xlnt_assert(ws3.print_title_rows().is_set());
+ xlnt_assert(ws3.print_title_cols().is_set());
+ xlnt_assert_equals(ws3.print_title_cols().get().first, "B");
+ xlnt_assert_equals(ws3.print_title_cols().get().second, "E");
+ xlnt_assert_equals(ws3.print_title_rows().get().first, 2);
+ xlnt_assert_equals(ws3.print_title_rows().get().second, 4);
+
+ wb.save("titles1.xlsx");
}
void test_freeze_panes_horiz()