Merge pull request #650 from tfussell/defined-names

Implement defined names
pull/651/head
Thomas Fussell 2022-08-21 11:09:55 -05:00 committed by GitHub
commit c689943a63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 287 additions and 97 deletions

View File

@ -652,37 +652,52 @@ public:
/// <summary>
/// Sets rows to repeat at top during printing.
/// </summary>
void print_title_rows(row_t first_row, row_t last_row);
void print_title_rows(row_t start, row_t end);
/// <summary>
/// Sets rows to repeat at top during printing.
/// Get rows to repeat at top during printing.
/// </summary>
void print_title_rows(row_t last_row);
optional<std::pair<row_t, row_t>> print_title_rows() const;
/// <summary>
/// Sets columns to repeat at left during printing.
/// </summary>
void print_title_cols(column_t first_column, column_t last_column);
void print_title_cols(column_t start, column_t end);
/// <summary>
/// Get columns to repeat at left during printing.
/// </summary>
optional<std::pair<column_t, column_t>> print_title_cols() const;
/// <summary>
/// Sets columns to repeat at left during printing.
/// Returns true if the sheet has print titles defined.
/// </summary>
void print_title_cols(column_t last_column);
bool has_print_titles() const;
/// <summary>
/// Returns a string representation of the defined print titles.
/// Remove all print titles.
/// </summary>
std::string print_titles() const;
void clear_print_titles();
/// <summary>
/// Sets the print area of this sheet to print_area.
/// </summary>
void print_area(const std::string &print_area);
/// <summary>
/// Clear the print area of this sheet.
/// </summary>
void clear_print_area();
/// <summary>
/// Returns the print area defined for this sheet.
/// </summary>
range_reference print_area() const;
/// <summary>
/// Returns true if the print area is defined for this sheet.
/// </summary>
bool has_print_area() const;
/// <summary>
/// Returns true if this sheet has any number of views defined.

View File

@ -143,9 +143,8 @@ struct worksheet_impl
optional<phonetic_pr> phonetic_properties_;
optional<header_footer> header_footer_;
std::string print_title_cols_;
std::string print_title_rows_;
optional<std::pair<column_t, column_t>> print_title_cols_;
optional<std::pair<row_t, row_t>> print_title_rows_;
optional<range_reference> print_area_;
std::vector<sheet_view> views_;

View File

@ -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 <string>
namespace xlnt {
namespace detail {
struct defined_name
{
std::string name;
std::size_t sheet_id;
bool hidden;
std::string value;
};
} // namespace detail
} // namespace xlnt

View File

@ -41,6 +41,7 @@
#include <detail/header_footer/header_footer_code.hpp>
#include <detail/implementations/workbook_impl.hpp>
#include <detail/serialization/custom_value_traits.hpp>
#include <detail/serialization/defined_name.hpp>
#include <detail/serialization/serialisation_helpers.hpp>
#include <detail/serialization/vector_streambuf.hpp>
#include <detail/serialization/xlsx_consumer.hpp>
@ -448,6 +449,70 @@ void xlsx_consumer::read_worksheet(const std::string &rel_id)
}
}
void read_defined_names(worksheet ws, std::vector<defined_name> 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<std::size_t>("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<std::string, std::string> &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;
}

View File

@ -56,6 +56,7 @@ namespace detail {
class izstream;
struct cell_impl;
struct defined_name;
struct worksheet_impl;
/// <summary>
@ -424,6 +425,8 @@ private:
detail::worksheet_impl *current_worksheet_;
number_serialiser converter_;
std::vector<defined_name> defined_names_;
};
} // namespace detail

View File

@ -41,6 +41,7 @@
#include <detail/header_footer/header_footer_code.hpp>
#include <detail/implementations/workbook_impl.hpp>
#include <detail/serialization/custom_value_traits.hpp>
#include <detail/serialization/defined_name.hpp>
#include <detail/serialization/vector_streambuf.hpp>
#include <detail/serialization/xlsx_producer.hpp>
#include <detail/serialization/zstream.hpp>
@ -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_name> 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");
}

View File

@ -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<std::pair<row_t, row_t>> 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<std::pair<column_t, column_t>> 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();

Binary file not shown.

View File

@ -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;

View File

@ -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<std::string>(), "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()