mirror of
https://github.com/tfussell/xlnt.git
synced 2024-03-22 13:11:17 +08:00
Merge branch 'tasmail-master-shared-strings-performance' into dev
remove less than comparators fix conflicts
This commit is contained in:
commit
0af7ad88e1
|
@ -22,7 +22,9 @@
|
||||||
// @author: see AUTHORS file
|
// @author: see AUTHORS file
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <string>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
|
@ -41,10 +43,8 @@ std::size_t random_index(std::size_t max)
|
||||||
return dis(gen);
|
return dis(gen);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<xlnt::style> generate_all_styles(xlnt::workbook &wb)
|
void generate_all_formats(xlnt::workbook &wb, std::vector<xlnt::format>& formats)
|
||||||
{
|
{
|
||||||
std::vector<xlnt::style> styles;
|
|
||||||
|
|
||||||
const auto vertical_alignments = std::vector<xlnt::vertical_alignment>
|
const auto vertical_alignments = std::vector<xlnt::vertical_alignment>
|
||||||
{
|
{
|
||||||
xlnt::vertical_alignment::center,
|
xlnt::vertical_alignment::center,
|
||||||
|
@ -94,8 +94,6 @@ std::vector<xlnt::style> generate_all_styles(xlnt::workbook &wb)
|
||||||
xlnt::font::underline_style::none
|
xlnt::font::underline_style::none
|
||||||
};
|
};
|
||||||
|
|
||||||
std::size_t index = 0;
|
|
||||||
|
|
||||||
for (auto vertical_alignment : vertical_alignments)
|
for (auto vertical_alignment : vertical_alignments)
|
||||||
{
|
{
|
||||||
for (auto horizontal_alignment : horizontal_alignments)
|
for (auto horizontal_alignment : horizontal_alignments)
|
||||||
|
@ -110,7 +108,7 @@ std::vector<xlnt::style> generate_all_styles(xlnt::workbook &wb)
|
||||||
{
|
{
|
||||||
for (auto italic : { true, false })
|
for (auto italic : { true, false })
|
||||||
{
|
{
|
||||||
auto s = wb.create_style(std::to_string(index++));
|
auto fmt = wb.create_format();
|
||||||
|
|
||||||
xlnt::font f;
|
xlnt::font f;
|
||||||
f.name(name);
|
f.name(name);
|
||||||
|
@ -118,14 +116,14 @@ std::vector<xlnt::style> generate_all_styles(xlnt::workbook &wb)
|
||||||
f.italic(italic);
|
f.italic(italic);
|
||||||
f.underline(underline);
|
f.underline(underline);
|
||||||
f.bold(bold);
|
f.bold(bold);
|
||||||
s.font(f);
|
fmt.font(f);
|
||||||
|
|
||||||
xlnt::alignment a;
|
xlnt::alignment a;
|
||||||
a.vertical(vertical_alignment);
|
a.vertical(vertical_alignment);
|
||||||
a.horizontal(horizontal_alignment);
|
a.horizontal(horizontal_alignment);
|
||||||
s.alignment(a);
|
fmt.alignment(a);
|
||||||
|
|
||||||
styles.push_back(s);
|
formats.push_back(fmt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,27 +131,47 @@ std::vector<xlnt::style> generate_all_styles(xlnt::workbook &wb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return styles;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
xlnt::workbook non_optimized_workbook(int n)
|
|
||||||
{
|
|
||||||
xlnt::workbook wb;
|
|
||||||
auto styles = generate_all_styles(wb);
|
|
||||||
|
|
||||||
for(int idx = 1; idx < n; idx++)
|
xlnt::workbook non_optimized_workbook_formats(int rows_number, int columns_number)
|
||||||
{
|
{
|
||||||
|
using xlnt::benchmarks::current_time;
|
||||||
|
|
||||||
|
xlnt::workbook wb;
|
||||||
|
std::vector<xlnt::format> formats;
|
||||||
|
auto start = current_time();
|
||||||
|
|
||||||
|
generate_all_formats(wb, formats);
|
||||||
|
|
||||||
|
auto elapsed = current_time() - start;
|
||||||
|
|
||||||
|
std::cout << "elapsed " << elapsed / 1000.0 << ". generate_all_formats. number of unique formats " << formats.size() << std::endl;
|
||||||
|
|
||||||
|
start = current_time();
|
||||||
auto worksheet = wb[random_index(wb.sheet_count())];
|
auto worksheet = wb[random_index(wb.sheet_count())];
|
||||||
auto cell = worksheet.cell(xlnt::cell_reference(1, (xlnt::row_t)idx));
|
auto cells_proceeded = 0;
|
||||||
cell.value(0);
|
for (int row_idx = 1; row_idx <= rows_number; row_idx++)
|
||||||
cell.style(styles.at(random_index(styles.size())));
|
{
|
||||||
|
for (int col_idx = 1; col_idx <= columns_number; col_idx++)
|
||||||
|
{
|
||||||
|
auto cell = worksheet.cell(xlnt::cell_reference((xlnt::column_t)col_idx, (xlnt::row_t)row_idx));
|
||||||
|
std::ostringstream string_stm;
|
||||||
|
string_stm << "Col: " << col_idx << "Row: " << row_idx;
|
||||||
|
cell.value(string_stm.str());
|
||||||
|
cell.format(formats.at(random_index(formats.size())));
|
||||||
|
cells_proceeded++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed = current_time() - start;
|
||||||
|
|
||||||
|
std::cout << "elapsed " << elapsed / 1000.0 << ". set values and formats for cells. cells proceeded " << cells_proceeded << std::endl;
|
||||||
|
|
||||||
return wb;
|
return wb;
|
||||||
}
|
}
|
||||||
|
|
||||||
void to_profile(xlnt::workbook &wb, const std::string &f, int n)
|
void to_save_profile(xlnt::workbook &wb, const std::string &f)
|
||||||
{
|
{
|
||||||
using xlnt::benchmarks::current_time;
|
using xlnt::benchmarks::current_time;
|
||||||
|
|
||||||
|
@ -161,17 +179,72 @@ void to_profile(xlnt::workbook &wb, const std::string &f, int n)
|
||||||
wb.save(f);
|
wb.save(f);
|
||||||
auto elapsed = current_time() - start;
|
auto elapsed = current_time() - start;
|
||||||
|
|
||||||
std::cout << "took " << elapsed / 1000.0 << "s for " << n << " styles" << std::endl;
|
std::cout << "elapsed " << elapsed / 1000.0 << ". save workbook." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void to_load_profile(xlnt::workbook &wb, const std::string &f)
|
||||||
|
{
|
||||||
|
using xlnt::benchmarks::current_time;
|
||||||
|
|
||||||
|
auto start = current_time();
|
||||||
|
wb.load(f);
|
||||||
|
auto elapsed = current_time() - start;
|
||||||
|
|
||||||
|
std::cout << "elapsed " << elapsed / 1000.0 << ". load workbook." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void read_formats_profile(xlnt::workbook &wb, int rows_number, int columns_number)
|
||||||
|
{
|
||||||
|
using xlnt::benchmarks::current_time;
|
||||||
|
|
||||||
|
std::vector<std::string> values;
|
||||||
|
std::vector<xlnt::format> formats;
|
||||||
|
auto start = current_time();
|
||||||
|
auto worksheet = wb[random_index(wb.sheet_count())];
|
||||||
|
for (int row_idx = 1; row_idx <= rows_number; row_idx++)
|
||||||
|
{
|
||||||
|
for (int col_idx = 1; col_idx <= columns_number; col_idx++)
|
||||||
|
{
|
||||||
|
auto cell = worksheet.cell(xlnt::cell_reference((xlnt::column_t)col_idx, (xlnt::row_t)row_idx));
|
||||||
|
values.push_back(cell.value<std::string>());
|
||||||
|
formats.push_back(cell.format());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto elapsed = current_time() - start;
|
||||||
|
|
||||||
|
std::cout << "elapsed " << elapsed / 1000.0 << ". read values and formats for cells. values count " << values.size()
|
||||||
|
<< ". formats count " << formats.size() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int main()
|
int main(int argc, char * argv[])
|
||||||
{
|
{
|
||||||
int n = 10000;
|
int rows_number = 1000;
|
||||||
auto wb = non_optimized_workbook(n);
|
int columns_number = 10;
|
||||||
std::string f = "temp.xlsx";
|
|
||||||
to_profile(wb, f, n);
|
try
|
||||||
|
{
|
||||||
|
if (argc > 1)
|
||||||
|
rows_number = std::stoi(argv[1]);
|
||||||
|
|
||||||
|
if (argc > 2)
|
||||||
|
columns_number = std::stoi(argv[2]);
|
||||||
|
|
||||||
|
std::cout << "started. number of rows " << rows_number << ", number of columns " << columns_number << std::endl;
|
||||||
|
auto wb = non_optimized_workbook_formats(rows_number, columns_number);
|
||||||
|
auto f = "temp-formats.xlsx";
|
||||||
|
to_save_profile(wb, f);
|
||||||
|
|
||||||
|
xlnt::workbook load_formats_wb;
|
||||||
|
to_load_profile(load_formats_wb, f);
|
||||||
|
read_formats_profile(load_formats_wb, rows_number, columns_number);
|
||||||
|
}
|
||||||
|
catch(std::exception& ex)
|
||||||
|
{
|
||||||
|
std::cout << "failed. " << ex.what() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,11 @@ public:
|
||||||
/// </summary>
|
/// </summary>
|
||||||
rich_text(const std::string &plain_text);
|
rich_text(const std::string &plain_text);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a rich text object from other
|
||||||
|
/// </summary>
|
||||||
|
rich_text(const rich_text &other);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a rich text object with the given text and font.
|
/// Constructs a rich text object with the given text and font.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -89,6 +94,11 @@ public:
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void add_run(const rich_text_run &t);
|
void add_run(const rich_text_run &t);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies rich text object from other
|
||||||
|
/// </summary>
|
||||||
|
rich_text& operator=(const rich_text &rhs);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if the runs that make up this text are identical to those in rhs.
|
/// Returns true if the runs that make up this text are identical to those in rhs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -116,4 +126,20 @@ private:
|
||||||
std::vector<rich_text_run> runs_;
|
std::vector<rich_text_run> runs_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class XLNT_API rich_text_hash
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::size_t operator()(const rich_text& k) const
|
||||||
|
{
|
||||||
|
std::size_t res = 0;
|
||||||
|
|
||||||
|
for (auto r : k.runs())
|
||||||
|
{
|
||||||
|
res ^= std::hash<std::string>()(r.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace xlnt
|
} // namespace xlnt
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <xlnt/xlnt_config.hpp>
|
#include <xlnt/xlnt_config.hpp>
|
||||||
|
#include <xlnt/cell/rich_text.hpp>
|
||||||
|
|
||||||
namespace xlnt {
|
namespace xlnt {
|
||||||
|
|
||||||
|
@ -760,16 +761,26 @@ public:
|
||||||
std::size_t add_shared_string(const rich_text &shared, bool allow_duplicates = false);
|
std::size_t add_shared_string(const rich_text &shared, bool allow_duplicates = false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a reference to the shared strings being used by cells
|
/// Returns a reference to the shared string ordered by id
|
||||||
/// in this workbook.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
std::vector<rich_text> &shared_strings();
|
const std::unordered_map<std::size_t, rich_text> &shared_strings_by_id() const;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a reference to the shared string related to the specified index
|
||||||
|
/// </summary>
|
||||||
|
const rich_text &shared_strings(std::size_t index) const;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a reference to the shared strings being used by cells
|
/// Returns a reference to the shared strings being used by cells
|
||||||
/// in this workbook.
|
/// in this workbook.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
const std::vector<rich_text> &shared_strings() const;
|
std::unordered_map<rich_text, std::size_t, rich_text_hash> &shared_strings();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a reference to the shared strings being used by cells
|
||||||
|
/// in this workbook.
|
||||||
|
/// </summary>
|
||||||
|
const std::unordered_map<rich_text, std::size_t, rich_text_hash> &shared_strings() const;
|
||||||
|
|
||||||
// Thumbnail
|
// Thumbnail
|
||||||
|
|
||||||
|
|
|
@ -704,7 +704,7 @@ XLNT_API rich_text cell::value() const
|
||||||
{
|
{
|
||||||
if (data_type() == cell::type::shared_string)
|
if (data_type() == cell::type::shared_string)
|
||||||
{
|
{
|
||||||
return workbook().shared_strings().at(static_cast<std::size_t>(d_->value_numeric_));
|
return workbook().shared_strings(static_cast<std::size_t>(d_->value_numeric_));
|
||||||
}
|
}
|
||||||
|
|
||||||
return d_->value_text_;
|
return d_->value_text_;
|
||||||
|
|
|
@ -45,6 +45,18 @@ rich_text::rich_text(const std::string &plain_text, const class font &text_font)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rich_text::rich_text(const rich_text &other)
|
||||||
|
{
|
||||||
|
*this = other;
|
||||||
|
}
|
||||||
|
|
||||||
|
rich_text &rich_text::operator=(const rich_text &rhs)
|
||||||
|
{
|
||||||
|
runs_.clear();
|
||||||
|
runs_ = rhs.runs_;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
rich_text::rich_text(const rich_text_run &single_run)
|
rich_text::rich_text(const rich_text_run &single_run)
|
||||||
{
|
{
|
||||||
add_run(single_run);
|
add_run(single_run);
|
||||||
|
@ -63,6 +75,11 @@ void rich_text::plain_text(const std::string &s, bool preserve_space = false)
|
||||||
|
|
||||||
std::string rich_text::plain_text() const
|
std::string rich_text::plain_text() const
|
||||||
{
|
{
|
||||||
|
if (runs_.size() == 1)
|
||||||
|
{
|
||||||
|
return runs_.begin()->first;
|
||||||
|
}
|
||||||
|
|
||||||
return std::accumulate(runs_.begin(), runs_.end(), std::string(),
|
return std::accumulate(runs_.begin(), runs_.end(), std::string(),
|
||||||
[](const std::string &a, const rich_text_run &run) { return a + run.first; });
|
[](const std::string &a, const rich_text_run &run) { return a + run.first; });
|
||||||
}
|
}
|
||||||
|
|
|
@ -547,7 +547,6 @@ std::string encode_header_footer(const rich_text &t, header_footer::location whe
|
||||||
case font::underline_style::double_accounting:
|
case font::underline_style::double_accounting:
|
||||||
encoded.append("&E");
|
encoded.append("&E");
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
case font::underline_style::none:
|
case font::underline_style::none:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,8 @@ struct workbook_impl
|
||||||
workbook_impl(const workbook_impl &other)
|
workbook_impl(const workbook_impl &other)
|
||||||
: active_sheet_index_(other.active_sheet_index_),
|
: active_sheet_index_(other.active_sheet_index_),
|
||||||
worksheets_(other.worksheets_),
|
worksheets_(other.worksheets_),
|
||||||
shared_strings_(other.shared_strings_),
|
shared_strings_ids_(other.shared_strings_ids_),
|
||||||
|
shared_strings_values_(other.shared_strings_values_),
|
||||||
stylesheet_(other.stylesheet_),
|
stylesheet_(other.stylesheet_),
|
||||||
manifest_(other.manifest_),
|
manifest_(other.manifest_),
|
||||||
theme_(other.theme_),
|
theme_(other.theme_),
|
||||||
|
@ -72,8 +73,8 @@ struct workbook_impl
|
||||||
active_sheet_index_ = other.active_sheet_index_;
|
active_sheet_index_ = other.active_sheet_index_;
|
||||||
worksheets_.clear();
|
worksheets_.clear();
|
||||||
std::copy(other.worksheets_.begin(), other.worksheets_.end(), back_inserter(worksheets_));
|
std::copy(other.worksheets_.begin(), other.worksheets_.end(), back_inserter(worksheets_));
|
||||||
shared_strings_.clear();
|
shared_strings_ids_ = other.shared_strings_ids_;
|
||||||
std::copy(other.shared_strings_.begin(), other.shared_strings_.end(), std::back_inserter(shared_strings_));
|
shared_strings_values_ = other.shared_strings_values_;
|
||||||
theme_ = other.theme_;
|
theme_ = other.theme_;
|
||||||
manifest_ = other.manifest_;
|
manifest_ = other.manifest_;
|
||||||
|
|
||||||
|
@ -116,7 +117,8 @@ struct workbook_impl
|
||||||
optional<std::size_t> active_sheet_index_;
|
optional<std::size_t> active_sheet_index_;
|
||||||
|
|
||||||
std::list<worksheet_impl> worksheets_;
|
std::list<worksheet_impl> worksheets_;
|
||||||
std::vector<rich_text> shared_strings_;
|
std::unordered_map<rich_text, std::size_t, rich_text_hash> shared_strings_ids_;
|
||||||
|
std::unordered_map<std::size_t, rich_text> shared_strings_values_;
|
||||||
|
|
||||||
optional<stylesheet> stylesheet_;
|
optional<stylesheet> stylesheet_;
|
||||||
|
|
||||||
|
@ -136,6 +138,7 @@ struct workbook_impl
|
||||||
optional<workbook_view> view_;
|
optional<workbook_view> view_;
|
||||||
optional<std::string> code_name_;
|
optional<std::string> code_name_;
|
||||||
|
|
||||||
|
<<<<<<< Updated upstream
|
||||||
struct file_version_t
|
struct file_version_t
|
||||||
{
|
{
|
||||||
std::string app_name;
|
std::string app_name;
|
||||||
|
@ -152,6 +155,16 @@ struct workbook_impl
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
=======
|
||||||
|
struct file_version_t
|
||||||
|
{
|
||||||
|
std::string app_name;
|
||||||
|
std::size_t last_edited;
|
||||||
|
std::size_t lowest_edited;
|
||||||
|
std::size_t rup_build;
|
||||||
|
};
|
||||||
|
|
||||||
|
>>>>>>> Stashed changes
|
||||||
optional<file_version_t> file_version_;
|
optional<file_version_t> file_version_;
|
||||||
optional<calculation_properties> calculation_properties_;
|
optional<calculation_properties> calculation_properties_;
|
||||||
optional<std::string> abs_path_;
|
optional<std::string> abs_path_;
|
||||||
|
|
|
@ -246,7 +246,11 @@ cell xlsx_consumer::read_cell()
|
||||||
|
|
||||||
if (parser().attribute_present("s"))
|
if (parser().attribute_present("s"))
|
||||||
{
|
{
|
||||||
|
<<<<<<< Updated upstream
|
||||||
cell.format(target_.format(static_cast<std::size_t>(std::stoull(parser().attribute("s")))));
|
cell.format(target_.format(static_cast<std::size_t>(std::stoull(parser().attribute("s")))));
|
||||||
|
=======
|
||||||
|
cell.format(target_.format(std::stoull(parser().attribute("s"))));
|
||||||
|
>>>>>>> Stashed changes
|
||||||
}
|
}
|
||||||
|
|
||||||
auto has_value = false;
|
auto has_value = false;
|
||||||
|
@ -536,12 +540,15 @@ std::string xlsx_consumer::read_worksheet_begin(const std::string &rel_id)
|
||||||
current_selection.active_cell(parser().attribute("activeCell"));
|
current_selection.active_cell(parser().attribute("activeCell"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< Updated upstream
|
||||||
if (parser().attribute_present("sqref"))
|
if (parser().attribute_present("sqref"))
|
||||||
{
|
{
|
||||||
const auto sqref = range_reference(parser().attribute("sqref"));
|
const auto sqref = range_reference(parser().attribute("sqref"));
|
||||||
current_selection.sqref(sqref);
|
current_selection.sqref(sqref);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> Stashed changes
|
||||||
current_selection.pane(pane_corner::top_left);
|
current_selection.pane(pane_corner::top_left);
|
||||||
|
|
||||||
new_view.add_selection(current_selection);
|
new_view.add_selection(current_selection);
|
||||||
|
@ -727,7 +734,11 @@ void xlsx_consumer::read_worksheet_sheetdata()
|
||||||
|
|
||||||
if (parser().attribute_present("s"))
|
if (parser().attribute_present("s"))
|
||||||
{
|
{
|
||||||
|
<<<<<<< Updated upstream
|
||||||
cell.format(target_.format(static_cast<std::size_t>(std::stoull(parser().attribute("s")))));
|
cell.format(target_.format(static_cast<std::size_t>(std::stoull(parser().attribute("s")))));
|
||||||
|
=======
|
||||||
|
cell.format(target_.format(std::stoull(parser().attribute("s"))));
|
||||||
|
>>>>>>> Stashed changes
|
||||||
}
|
}
|
||||||
|
|
||||||
auto has_value = false;
|
auto has_value = false;
|
||||||
|
@ -1021,14 +1032,14 @@ worksheet xlsx_consumer::read_worksheet_end(const std::string &rel_id)
|
||||||
{
|
{
|
||||||
header_footer hf;
|
header_footer hf;
|
||||||
|
|
||||||
hf.align_with_margins(
|
hf.align_with_margins(!parser().attribute_present("alignWithMargins")
|
||||||
!parser().attribute_present("alignWithMargins") || is_true(parser().attribute("alignWithMargins")));
|
|| is_true(parser().attribute("alignWithMargins")));
|
||||||
hf.scale_with_doc(
|
hf.scale_with_doc(!parser().attribute_present("alignWithMargins")
|
||||||
!parser().attribute_present("alignWithMargins") || is_true(parser().attribute("alignWithMargins")));
|
|| is_true(parser().attribute("alignWithMargins")));
|
||||||
auto different_odd_even =
|
auto different_odd_even = parser().attribute_present("differentOddEven")
|
||||||
parser().attribute_present("differentOddEven") && is_true(parser().attribute("differentOddEven"));
|
&& is_true(parser().attribute("differentOddEven"));
|
||||||
auto different_first =
|
auto different_first = parser().attribute_present("differentFirst")
|
||||||
parser().attribute_present("differentFirst") && is_true(parser().attribute("differentFirst"));
|
&& is_true(parser().attribute("differentFirst"));
|
||||||
|
|
||||||
optional<std::array<optional<rich_text>, 3>> odd_header;
|
optional<std::array<optional<rich_text>, 3>> odd_header;
|
||||||
optional<std::array<optional<rich_text>, 3>> odd_footer;
|
optional<std::array<optional<rich_text>, 3>> odd_footer;
|
||||||
|
@ -1082,13 +1093,17 @@ worksheet xlsx_consumer::read_worksheet_end(const std::string &rel_id)
|
||||||
|
|
||||||
if (different_odd_even)
|
if (different_odd_even)
|
||||||
{
|
{
|
||||||
if (odd_header.is_set() && odd_header.get().at(i).is_set() && even_header.is_set()
|
if (odd_header.is_set()
|
||||||
|
&& odd_header.get().at(i).is_set()
|
||||||
|
&& even_header.is_set()
|
||||||
&& even_header.get().at(i).is_set())
|
&& even_header.get().at(i).is_set())
|
||||||
{
|
{
|
||||||
hf.odd_even_header(loc, odd_header.get().at(i).get(), even_header.get().at(i).get());
|
hf.odd_even_header(loc, odd_header.get().at(i).get(), even_header.get().at(i).get());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (odd_footer.is_set() && odd_footer.get().at(i).is_set() && even_footer.is_set()
|
if (odd_footer.is_set()
|
||||||
|
&& odd_footer.get().at(i).is_set()
|
||||||
|
&& even_footer.is_set()
|
||||||
&& even_footer.get().at(i).is_set())
|
&& even_footer.get().at(i).is_set())
|
||||||
{
|
{
|
||||||
hf.odd_even_footer(loc, odd_footer.get().at(i).get(), even_footer.get().at(i).get());
|
hf.odd_even_footer(loc, odd_footer.get().at(i).get(), even_footer.get().at(i).get());
|
||||||
|
@ -1915,18 +1930,17 @@ void xlsx_consumer::read_shared_string_table()
|
||||||
unique_count = parser().attribute<std::size_t>("uniqueCount");
|
unique_count = parser().attribute<std::size_t>("uniqueCount");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &strings = target_.shared_strings();
|
|
||||||
|
|
||||||
while (in_element(qn("spreadsheetml", "sst")))
|
while (in_element(qn("spreadsheetml", "sst")))
|
||||||
{
|
{
|
||||||
expect_start_element(qn("spreadsheetml", "si"), xml::content::complex);
|
expect_start_element(qn("spreadsheetml", "si"), xml::content::complex);
|
||||||
strings.push_back(read_rich_text(qn("spreadsheetml", "si")));
|
auto rt = read_rich_text(qn("spreadsheetml", "si"));
|
||||||
|
target_.add_shared_string(rt);
|
||||||
expect_end_element(qn("spreadsheetml", "si"));
|
expect_end_element(qn("spreadsheetml", "si"));
|
||||||
}
|
}
|
||||||
|
|
||||||
expect_end_element(qn("spreadsheetml", "sst"));
|
expect_end_element(qn("spreadsheetml", "sst"));
|
||||||
|
|
||||||
if (has_unique_count && unique_count != strings.size())
|
if (has_unique_count && unique_count != target_.shared_strings().size())
|
||||||
{
|
{
|
||||||
throw invalid_file("sizes don't match");
|
throw invalid_file("sizes don't match");
|
||||||
}
|
}
|
||||||
|
|
|
@ -850,15 +850,17 @@ void xlsx_producer::write_shared_string_table(const relationship & /*rel*/)
|
||||||
#pragma clang diagnostic pop
|
#pragma clang diagnostic pop
|
||||||
|
|
||||||
write_attribute("count", string_count);
|
write_attribute("count", string_count);
|
||||||
write_attribute("uniqueCount", source_.shared_strings().size());
|
write_attribute("uniqueCount", source_.shared_strings_by_id().size());
|
||||||
|
|
||||||
for (const auto &string : source_.shared_strings())
|
for (const auto &string : source_.shared_strings_by_id())
|
||||||
{
|
{
|
||||||
if (string.runs().size() == 1 && !string.runs().at(0).second.is_set())
|
if (string.second.runs().size() == 1 && !string.second.runs().at(0).second.is_set())
|
||||||
{
|
{
|
||||||
write_start_element(xmlns, "si");
|
write_start_element(xmlns, "si");
|
||||||
write_start_element(xmlns, "t");
|
write_start_element(xmlns, "t");
|
||||||
write_characters(string.plain_text(), string.runs().front().preserve_space);
|
|
||||||
|
write_characters(string.second.plain_text(), string.second.runs().front().preserve_space);
|
||||||
|
|
||||||
write_end_element(xmlns, "t");
|
write_end_element(xmlns, "t");
|
||||||
write_end_element(xmlns, "si");
|
write_end_element(xmlns, "si");
|
||||||
|
|
||||||
|
@ -867,7 +869,7 @@ void xlsx_producer::write_shared_string_table(const relationship & /*rel*/)
|
||||||
|
|
||||||
write_start_element(xmlns, "si");
|
write_start_element(xmlns, "si");
|
||||||
|
|
||||||
for (const auto &run : string.runs())
|
for (const auto &run : string.second.runs())
|
||||||
{
|
{
|
||||||
write_start_element(xmlns, "r");
|
write_start_element(xmlns, "r");
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iterator>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
|
@ -137,8 +139,8 @@ path::path()
|
||||||
}
|
}
|
||||||
|
|
||||||
path::path(const std::string &path_string)
|
path::path(const std::string &path_string)
|
||||||
: internal_(path_string)
|
|
||||||
{
|
{
|
||||||
|
std::remove_copy(path_string.begin(), path_string.end(), std::back_inserter(internal_), '\"');
|
||||||
}
|
}
|
||||||
|
|
||||||
path::path(const std::string &path_string, char sep)
|
path::path(const std::string &path_string, char sep)
|
||||||
|
|
|
@ -473,11 +473,15 @@ workbook workbook::empty()
|
||||||
.color(theme_color(1));
|
.color(theme_color(1));
|
||||||
stylesheet.fonts.push_back(default_font);
|
stylesheet.fonts.push_back(default_font);
|
||||||
|
|
||||||
|
<<<<<<< Updated upstream
|
||||||
wb.create_builtin_style(0)
|
wb.create_builtin_style(0)
|
||||||
.border(default_border)
|
.border(default_border)
|
||||||
.fill(default_fill)
|
.fill(default_fill)
|
||||||
.font(default_font)
|
.font(default_font)
|
||||||
.number_format(xlnt::number_format::general());
|
.number_format(xlnt::number_format::general());
|
||||||
|
=======
|
||||||
|
wb.create_builtin_style(0);
|
||||||
|
>>>>>>> Stashed changes
|
||||||
|
|
||||||
wb.create_format(true)
|
wb.create_format(true)
|
||||||
.border(default_border)
|
.border(default_border)
|
||||||
|
@ -1017,8 +1021,8 @@ void workbook::load(const std::wstring &filename, const std::string &password)
|
||||||
|
|
||||||
void workbook::remove_sheet(worksheet ws)
|
void workbook::remove_sheet(worksheet ws)
|
||||||
{
|
{
|
||||||
auto match_iter = std::find_if(
|
auto match_iter = std::find_if(d_->worksheets_.begin(), d_->worksheets_.end(),
|
||||||
d_->worksheets_.begin(), d_->worksheets_.end(), [=](detail::worksheet_impl &comp) { return &comp == ws.d_; });
|
[=](detail::worksheet_impl &comp) { return &comp == ws.d_; });
|
||||||
|
|
||||||
if (match_iter == d_->worksheets_.end())
|
if (match_iter == d_->worksheets_.end())
|
||||||
{
|
{
|
||||||
|
@ -1071,10 +1075,10 @@ worksheet workbook::create_sheet_with_rel(const std::string &title, const relati
|
||||||
|
|
||||||
auto workbook_rel = d_->manifest_.relationship(path("/"), relationship_type::office_document);
|
auto workbook_rel = d_->manifest_.relationship(path("/"), relationship_type::office_document);
|
||||||
auto sheet_absoulute_path = workbook_rel.target().path().parent().append(rel.target().path());
|
auto sheet_absoulute_path = workbook_rel.target().path().parent().append(rel.target().path());
|
||||||
d_->manifest_.register_override_type(
|
d_->manifest_.register_override_type(sheet_absoulute_path,
|
||||||
sheet_absoulute_path, "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml");
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml");
|
||||||
auto ws_rel = d_->manifest_.register_relationship(
|
auto ws_rel = d_->manifest_.register_relationship(workbook_rel.target(),
|
||||||
workbook_rel.target(), relationship_type::worksheet, rel.target(), target_mode::internal);
|
relationship_type::worksheet, rel.target(), target_mode::internal);
|
||||||
d_->sheet_title_rel_id_map_[title] = ws_rel;
|
d_->sheet_title_rel_id_map_[title] = ws_rel;
|
||||||
|
|
||||||
update_sheet_properties();
|
update_sheet_properties();
|
||||||
|
@ -1332,39 +1336,53 @@ const manifest &workbook::manifest() const
|
||||||
return d_->manifest_;
|
return d_->manifest_;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<rich_text> &workbook::shared_strings()
|
const std::unordered_map<std::size_t, rich_text> &workbook::shared_strings_by_id() const
|
||||||
{
|
{
|
||||||
return d_->shared_strings_;
|
return d_->shared_strings_values_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<rich_text> &workbook::shared_strings() const
|
const rich_text& workbook::shared_strings(std::size_t index) const
|
||||||
{
|
{
|
||||||
return d_->shared_strings_;
|
auto it = d_->shared_strings_values_.find(index);
|
||||||
|
|
||||||
|
if (it != d_->shared_strings_values_.end())
|
||||||
|
{
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
static rich_text empty;
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<rich_text, std::size_t, rich_text_hash> &workbook::shared_strings()
|
||||||
|
{
|
||||||
|
return d_->shared_strings_ids_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::unordered_map<rich_text, std::size_t, rich_text_hash> &workbook::shared_strings() const
|
||||||
|
{
|
||||||
|
return d_->shared_strings_ids_;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t workbook::add_shared_string(const rich_text &shared, bool allow_duplicates)
|
std::size_t workbook::add_shared_string(const rich_text &shared, bool allow_duplicates)
|
||||||
{
|
{
|
||||||
register_workbook_part(relationship_type::shared_string_table);
|
register_workbook_part(relationship_type::shared_string_table);
|
||||||
|
|
||||||
auto index = std::size_t(0);
|
|
||||||
|
|
||||||
if (!allow_duplicates)
|
if (!allow_duplicates)
|
||||||
{
|
{
|
||||||
// TODO: inefficient, use a set or something?
|
auto it = d_->shared_strings_ids_.find(shared);
|
||||||
for (auto &s : d_->shared_strings_)
|
|
||||||
|
if (it != d_->shared_strings_ids_.end())
|
||||||
{
|
{
|
||||||
if (s == shared)
|
return it->second;
|
||||||
{
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
++index;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d_->shared_strings_.push_back(shared);
|
auto sz = d_->shared_strings_ids_.size();
|
||||||
|
d_->shared_strings_ids_[shared] = sz;
|
||||||
|
d_->shared_strings_values_[sz] = shared;
|
||||||
|
|
||||||
return index;
|
return sz;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool workbook::contains(const std::string &sheet_title) const
|
bool workbook::contains(const std::string &sheet_title) const
|
||||||
|
|
|
@ -110,6 +110,7 @@ public:
|
||||||
const auto path = path_helper::test_file("3_default.xlsx");
|
const auto path = path_helper::test_file("3_default.xlsx");
|
||||||
xlnt_assert(workbook_matches_file(wb, path));
|
xlnt_assert(workbook_matches_file(wb, path));
|
||||||
}
|
}
|
||||||
|
<<<<<<< Updated upstream:tests/workbook/serialization_test_suite.cpp
|
||||||
|
|
||||||
void test_produce_simple_excel()
|
void test_produce_simple_excel()
|
||||||
{
|
{
|
||||||
|
@ -245,6 +246,103 @@ public:
|
||||||
|
|
||||||
sheet1.cell("A5").hyperlink("https://google.com/");
|
sheet1.cell("A5").hyperlink("https://google.com/");
|
||||||
sheet1.cell("A5").format(hyperlink_format);
|
sheet1.cell("A5").format(hyperlink_format);
|
||||||
|
=======
|
||||||
|
|
||||||
|
void test_produce_simple_excel()
|
||||||
|
{
|
||||||
|
xlnt::workbook wb;
|
||||||
|
auto ws = wb.active_sheet();
|
||||||
|
|
||||||
|
auto bold_font = xlnt::font().bold(true);
|
||||||
|
|
||||||
|
ws.cell("A1").value("Type");
|
||||||
|
ws.cell("A1").font(bold_font);
|
||||||
|
|
||||||
|
ws.cell("B1").value("Value");
|
||||||
|
ws.cell("B1").font(bold_font);
|
||||||
|
|
||||||
|
ws.cell("A2").value("null");
|
||||||
|
ws.cell("B2").value(nullptr);
|
||||||
|
|
||||||
|
ws.cell("A3").value("bool (true)");
|
||||||
|
ws.cell("B3").value(true);
|
||||||
|
|
||||||
|
ws.cell("A4").value("bool (false)");
|
||||||
|
ws.cell("B4").value(false);
|
||||||
|
|
||||||
|
ws.cell("A5").value("number (int)");
|
||||||
|
ws.cell("B5").value(std::numeric_limits<int>::max());
|
||||||
|
|
||||||
|
ws.cell("A5").value("number (unsigned int)");
|
||||||
|
ws.cell("B5").value(std::numeric_limits<unsigned int>::max());
|
||||||
|
|
||||||
|
ws.cell("A6").value("number (long long int)");
|
||||||
|
ws.cell("B6").value(std::numeric_limits<long long int>::max());
|
||||||
|
|
||||||
|
ws.cell("A6").value("number (unsigned long long int)");
|
||||||
|
ws.cell("B6").value(std::numeric_limits<unsigned long long int>::max());
|
||||||
|
|
||||||
|
ws.cell("A13").value("number (float)");
|
||||||
|
ws.cell("B13").value(std::numeric_limits<float>::max());
|
||||||
|
|
||||||
|
ws.cell("A14").value("number (double)");
|
||||||
|
ws.cell("B14").value(std::numeric_limits<double>::max());
|
||||||
|
|
||||||
|
ws.cell("A16").value("text (char *)");
|
||||||
|
ws.cell("B16").value("string");
|
||||||
|
|
||||||
|
ws.cell("A17").value("text (std::string)");
|
||||||
|
ws.cell("B17").value(std::string("string"));
|
||||||
|
|
||||||
|
ws.cell("A18").value("date");
|
||||||
|
ws.cell("B18").value(xlnt::date(2016, 2, 3));
|
||||||
|
|
||||||
|
ws.cell("A19").value("time");
|
||||||
|
ws.cell("B19").value(xlnt::time(1, 2, 3, 4));
|
||||||
|
|
||||||
|
ws.cell("A20").value("datetime");
|
||||||
|
ws.cell("B20").value(xlnt::datetime(2016, 2, 3, 1, 2, 3, 4));
|
||||||
|
|
||||||
|
ws.cell("A21").value("timedelta");
|
||||||
|
ws.cell("B21").value(xlnt::timedelta(1, 2, 3, 4, 5));
|
||||||
|
|
||||||
|
ws.freeze_panes("B2");
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> temp_buffer;
|
||||||
|
wb.save(temp_buffer);
|
||||||
|
xlnt_assert(!temp_buffer.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_save_after_sheet_deletion()
|
||||||
|
{
|
||||||
|
xlnt::workbook workbook;
|
||||||
|
|
||||||
|
xlnt_assert_equals(workbook.sheet_titles().size(), 1);
|
||||||
|
|
||||||
|
auto sheet = workbook.create_sheet();
|
||||||
|
sheet.title("XXX1");
|
||||||
|
xlnt_assert_equals(workbook.sheet_titles().size(), 2);
|
||||||
|
|
||||||
|
workbook.remove_sheet(workbook.sheet_by_title("XXX1"));
|
||||||
|
xlnt_assert_equals(workbook.sheet_titles().size(), 1);
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> temp_buffer;
|
||||||
|
xlnt_assert_throws_nothing(workbook.save(temp_buffer));
|
||||||
|
xlnt_assert(!temp_buffer.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_write_comments_hyperlinks_formulae()
|
||||||
|
{
|
||||||
|
xlnt::workbook wb;
|
||||||
|
auto sheet1 = wb.active_sheet();
|
||||||
|
auto comment_font = xlnt::font().bold(true).size(10).color(xlnt::indexed_color(81)).name("Calibri");
|
||||||
|
|
||||||
|
sheet1.cell("A1").value("Sheet1!A1");
|
||||||
|
sheet1.cell("A1").comment("Sheet1 comment", comment_font, "Microsoft Office User");
|
||||||
|
|
||||||
|
sheet1.cell("A2").value("Sheet1!A2");
|
||||||
|
sheet1.cell("A2").comment("Sheet1 comment2", comment_font, "Microsoft Office User");
|
||||||
|
>>>>>>> Stashed changes:tests/workbook/serialization_test_suite.hpp
|
||||||
|
|
||||||
sheet1.cell("A6").hyperlink(sheet1.cell("A1"));
|
sheet1.cell("A6").hyperlink(sheet1.cell("A1"));
|
||||||
sheet1.cell("A6").format(hyperlink_format);
|
sheet1.cell("A6").format(hyperlink_format);
|
||||||
|
@ -257,6 +355,7 @@ public:
|
||||||
sheet1.cell("C2").value("a");
|
sheet1.cell("C2").value("a");
|
||||||
sheet1.cell("C3").value("b");
|
sheet1.cell("C3").value("b");
|
||||||
|
|
||||||
|
<<<<<<< Updated upstream:tests/workbook/serialization_test_suite.cpp
|
||||||
for (auto i = 1; i <= 7; ++i)
|
for (auto i = 1; i <= 7; ++i)
|
||||||
{
|
{
|
||||||
sheet1.row_properties(i).dy_descent = 0.2;
|
sheet1.row_properties(i).dy_descent = 0.2;
|
||||||
|
@ -270,6 +369,11 @@ public:
|
||||||
// comments
|
// comments
|
||||||
sheet2.cell("A1").value("Sheet2!A1");
|
sheet2.cell("A1").value("Sheet2!A1");
|
||||||
sheet2.cell("A1").comment("Sheet2 comment", comment_font, "Microsoft Office User");
|
sheet2.cell("A1").comment("Sheet2 comment", comment_font, "Microsoft Office User");
|
||||||
|
=======
|
||||||
|
auto sheet2 = wb.create_sheet();
|
||||||
|
sheet2.cell("A1").value("Sheet2!A1");
|
||||||
|
sheet2.cell("A2").comment("Sheet2 comment", comment_font, "Microsoft Office User");
|
||||||
|
>>>>>>> Stashed changes:tests/workbook/serialization_test_suite.hpp
|
||||||
|
|
||||||
sheet2.cell("A2").value("Sheet2!A2");
|
sheet2.cell("A2").value("Sheet2!A2");
|
||||||
sheet2.cell("A2").comment("Sheet2 comment2", comment_font, "Microsoft Office User");
|
sheet2.cell("A2").comment("Sheet2 comment2", comment_font, "Microsoft Office User");
|
||||||
|
@ -360,11 +464,17 @@ public:
|
||||||
{
|
{
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
xlnt::workbook wb;
|
xlnt::workbook wb;
|
||||||
|
<<<<<<< Updated upstream:tests/workbook/serialization_test_suite.cpp
|
||||||
// L"/9_unicode_Λ.xlsx" doesn't use wchar_t(0x039B) for the capital lambda...
|
// L"/9_unicode_Λ.xlsx" doesn't use wchar_t(0x039B) for the capital lambda...
|
||||||
// L"/9_unicode_\u039B.xlsx" gives the corrct output
|
// L"/9_unicode_\u039B.xlsx" gives the corrct output
|
||||||
const auto path = LSTRING_LITERAL(XLNT_TEST_DATA_DIR) L"/9_unicode_\u039B.xlsx"; // L"/9_unicode_Λ.xlsx"
|
const auto path = LSTRING_LITERAL(XLNT_TEST_DATA_DIR) L"/9_unicode_\u039B.xlsx"; // L"/9_unicode_Λ.xlsx"
|
||||||
wb.load(path);
|
wb.load(path);
|
||||||
xlnt_assert_equals(wb.active_sheet().cell("A1").value<std::string>(), u8"un\u00EFc\u00F4d\u0117!"); // u8"unïcôdė!"
|
xlnt_assert_equals(wb.active_sheet().cell("A1").value<std::string>(), u8"un\u00EFc\u00F4d\u0117!"); // u8"unïcôdė!"
|
||||||
|
=======
|
||||||
|
std::wstring path = LSTRING_LITERAL(XLNT_TEST_DATA_DIR) L"/9_unicode_Λ.xlsx";
|
||||||
|
wb.load(path_normalized);
|
||||||
|
xlnt_assert_equals(wb.active_sheet().cell("A1").value<std::string>(), u8"unicodê!");
|
||||||
|
>>>>>>> Stashed changes:tests/workbook/serialization_test_suite.hpp
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef __MINGW32__
|
#ifndef __MINGW32__
|
||||||
|
@ -560,8 +670,13 @@ public:
|
||||||
ws.column_properties("E").width = width;
|
ws.column_properties("E").width = width;
|
||||||
ws.column_properties("E").custom_width = true;
|
ws.column_properties("E").custom_width = true;
|
||||||
|
|
||||||
|
<<<<<<< Updated upstream:tests/workbook/serialization_test_suite.cpp
|
||||||
xlnt_assert(workbook_matches_file(wb,
|
xlnt_assert(workbook_matches_file(wb,
|
||||||
path_helper::test_file("13_custom_heights_widths.xlsx")));
|
path_helper::test_file("13_custom_heights_widths.xlsx")));
|
||||||
|
=======
|
||||||
|
wb.save("temp.xlsx");
|
||||||
|
xlnt_assert(workbook_matches_file(wb, path_helper::test_file("13_custom_heights_widths.xlsx")));
|
||||||
|
>>>>>>> Stashed changes:tests/workbook/serialization_test_suite.hpp
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user