mirror of
https://github.com/tfussell/xlnt.git
synced 2024-03-22 13:11:17 +08:00
Merge branch 'dev' into issue165-xlsx-producer
This commit is contained in:
commit
7c9443dca9
|
@ -54,6 +54,7 @@ class workbook;
|
|||
class worksheet;
|
||||
class xlsx_consumer;
|
||||
class xlsx_producer;
|
||||
class phonetic_pr;
|
||||
|
||||
struct date;
|
||||
struct datetime;
|
||||
|
@ -501,6 +502,18 @@ public:
|
|||
/// </summary>
|
||||
void merged(bool merged);
|
||||
|
||||
// phonetics
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this cell is set to show phonetic information.
|
||||
/// </summary>
|
||||
bool phonetics_visible() const;
|
||||
|
||||
/// <summary>
|
||||
/// Enables the display of phonetic information on this cell.
|
||||
/// </summary>
|
||||
void show_phonetics(bool phonetics);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the error string that is stored in this cell.
|
||||
/// </summary>
|
||||
|
|
|
@ -283,6 +283,12 @@ public:
|
|||
index_t index;
|
||||
};
|
||||
|
||||
enum class row_or_col_t : int
|
||||
{
|
||||
row,
|
||||
column
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Functor for hashing a column.
|
||||
/// Allows for use of std::unordered_set<column_t, column_hash> and similar.
|
||||
|
|
46
include/xlnt/cell/phonetic_run.hpp
Normal file
46
include/xlnt/cell/phonetic_run.hpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) 2016-2018
|
||||
//
|
||||
// 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, WRISING 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>
|
||||
|
||||
#include <xlnt/xlnt_config.hpp>
|
||||
|
||||
namespace xlnt {
|
||||
|
||||
/// <summary>
|
||||
/// Encapsulates a run of text that
|
||||
/// </summary>
|
||||
struct XLNT_API phonetic_run
|
||||
{
|
||||
std::string text;
|
||||
uint32_t start;
|
||||
uint32_t end;
|
||||
bool preserve_space;
|
||||
|
||||
bool operator==(const phonetic_run &other) const;
|
||||
bool operator!=(const phonetic_run &other) const;
|
||||
};
|
||||
|
||||
} // namespace xlnt
|
|
@ -28,6 +28,8 @@
|
|||
|
||||
#include <xlnt/xlnt_config.hpp>
|
||||
#include <xlnt/cell/rich_text_run.hpp>
|
||||
#include <xlnt/cell/phonetic_run.hpp>
|
||||
#include <xlnt/worksheet/phonetic_pr.hpp>
|
||||
|
||||
namespace xlnt {
|
||||
|
||||
|
@ -94,6 +96,36 @@ public:
|
|||
/// </summary>
|
||||
void add_run(const rich_text_run &t);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a copy of the individual runs that comprise this text.
|
||||
/// </summary>
|
||||
std::vector<phonetic_run> phonetic_runs() const;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the runs of this text all at once.
|
||||
/// </summary>
|
||||
void phonetic_runs(const std::vector<phonetic_run> &new_phonetic_runs);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new run to the end of the set of runs.
|
||||
/// </summary>
|
||||
void add_phonetic_run(const phonetic_run &t);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this text has phonetic properties
|
||||
/// </summary>
|
||||
bool has_phonetic_properties() const;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the phonetic properties of this text.
|
||||
/// </summary>
|
||||
const phonetic_pr &phonetic_properties() const;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the phonetic properties of this text to phonetic_props
|
||||
/// </summary>
|
||||
void phonetic_properties(const phonetic_pr& phonetic_props);
|
||||
|
||||
/// <summary>
|
||||
/// Copies rich text object from other
|
||||
/// </summary>
|
||||
|
@ -124,6 +156,8 @@ private:
|
|||
/// The runs that make up this rich text.
|
||||
/// </summary>
|
||||
std::vector<rich_text_run> runs_;
|
||||
std::vector<phonetic_run> phonetic_runs_;
|
||||
optional<phonetic_pr> phonetic_properties_;
|
||||
};
|
||||
|
||||
class XLNT_API rich_text_hash
|
||||
|
|
|
@ -63,6 +63,13 @@ public:
|
|||
/// The index to the style used by all cells in this row
|
||||
/// </summary>
|
||||
optional<std::size_t> style;
|
||||
|
||||
/// <summary>
|
||||
/// The row column span, used as part of the row block optimisation.
|
||||
/// This used for loading this attribute from existing excel files mainly for inspecting
|
||||
/// and not used when saving, it is calculated in the xlsx_producer.
|
||||
/// </summary>
|
||||
optional<std::string> spans;
|
||||
};
|
||||
|
||||
inline bool operator==(const row_properties &lhs, const row_properties &rhs)
|
||||
|
@ -72,7 +79,8 @@ inline bool operator==(const row_properties &lhs, const row_properties &rhs)
|
|||
&& lhs.custom_height == rhs.custom_height
|
||||
&& lhs.hidden == rhs.hidden
|
||||
&& lhs.custom_format == rhs.custom_format
|
||||
&& lhs.style == rhs.style;
|
||||
&& lhs.style == rhs.style
|
||||
&& lhs.spans == rhs.spans;
|
||||
}
|
||||
|
||||
} // namespace xlnt
|
||||
|
|
|
@ -270,6 +270,26 @@ public:
|
|||
/// </summary>
|
||||
void clear_row(row_t row);
|
||||
|
||||
/// <summary>
|
||||
/// Insert empty rows before the given row index
|
||||
/// </summary>
|
||||
void insert_rows(row_t row, std::uint32_t amount);
|
||||
|
||||
/// <summary>
|
||||
/// Insert empty columns before the given column index
|
||||
/// </summary>
|
||||
void insert_columns(column_t column, std::uint32_t amount);
|
||||
|
||||
/// <summary>
|
||||
/// Delete rows before the given row index
|
||||
/// </summary>
|
||||
void delete_rows(row_t row, std::uint32_t amount);
|
||||
|
||||
/// <summary>
|
||||
/// Delete columns before the given column index
|
||||
/// </summary>
|
||||
void delete_columns(column_t column, std::uint32_t amount);
|
||||
|
||||
// properties
|
||||
|
||||
/// <summary>
|
||||
|
@ -778,6 +798,12 @@ private:
|
|||
/// </summary>
|
||||
void parent(class workbook &wb);
|
||||
|
||||
/// <summary>
|
||||
/// Move cells after index down or right by a given amount. The direction is decided by row_or_col.
|
||||
/// If reverse is true, the cells will be moved up or left, depending on row_or_col.
|
||||
/// </summary>
|
||||
void move_cells(std::uint32_t index, std::uint32_t amount, row_or_col_t row_or_col, bool reverse = false);
|
||||
|
||||
/// <summary>
|
||||
/// The pointer to this sheet's implementation.
|
||||
/// </summary>
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
#include <xlnt/worksheet/column_properties.hpp>
|
||||
#include <xlnt/worksheet/row_properties.hpp>
|
||||
#include <xlnt/worksheet/worksheet.hpp>
|
||||
#include <xlnt/worksheet/phonetic_pr.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -198,7 +199,7 @@ cell::cell(detail::cell_impl *d)
|
|||
|
||||
bool cell::garbage_collectible() const
|
||||
{
|
||||
return !(has_value() || is_merged() || has_formula() || has_format() || has_hyperlink());
|
||||
return !(has_value() || is_merged() || phonetics_visible() || has_formula() || has_format() || has_hyperlink());
|
||||
}
|
||||
|
||||
void cell::value(std::nullptr_t)
|
||||
|
@ -329,6 +330,16 @@ bool cell::is_merged() const
|
|||
return d_->is_merged_;
|
||||
}
|
||||
|
||||
bool cell::phonetics_visible() const
|
||||
{
|
||||
return d_->phonetics_visible_;
|
||||
}
|
||||
|
||||
void cell::show_phonetics(bool phonetics)
|
||||
{
|
||||
d_->phonetics_visible_ = phonetics;
|
||||
}
|
||||
|
||||
bool cell::is_date() const
|
||||
{
|
||||
return data_type() == type::number
|
||||
|
@ -360,7 +371,7 @@ hyperlink cell::hyperlink() const
|
|||
|
||||
void cell::hyperlink(const std::string &url, const std::string &display)
|
||||
{
|
||||
if (url.empty() || std::find(url.begin(), url.end(), ':') == url.end())
|
||||
if (url.empty())
|
||||
{
|
||||
throw invalid_parameter();
|
||||
}
|
||||
|
@ -846,6 +857,12 @@ void cell::clear_style()
|
|||
void cell::style(const class style &new_style)
|
||||
{
|
||||
auto new_format = has_format() ? format() : workbook().create_format();
|
||||
|
||||
new_format.border(new_style.border());
|
||||
new_format.fill(new_style.fill());
|
||||
new_format.font(new_style.font());
|
||||
new_format.number_format(new_style.number_format());
|
||||
|
||||
format(new_format.style(new_style));
|
||||
}
|
||||
|
||||
|
|
40
source/cell/phonetic_run.cpp
Normal file
40
source/cell/phonetic_run.cpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) 2014-2018
|
||||
//
|
||||
// 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, WRISING 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
|
||||
|
||||
#include <xlnt/cell/phonetic_run.hpp>
|
||||
#include <tuple>
|
||||
|
||||
namespace xlnt {
|
||||
|
||||
bool phonetic_run::operator==(const phonetic_run &other) const
|
||||
{
|
||||
return std::tie(text, start, end, preserve_space) ==
|
||||
std::tie(other.text, other.start, other.end, other.preserve_space);
|
||||
}
|
||||
|
||||
bool phonetic_run::operator!=(const phonetic_run &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
} // namespace xlnt
|
|
@ -52,8 +52,10 @@ rich_text::rich_text(const rich_text &other)
|
|||
|
||||
rich_text &rich_text::operator=(const rich_text &rhs)
|
||||
{
|
||||
runs_.clear();
|
||||
clear();
|
||||
runs_ = rhs.runs_;
|
||||
phonetic_runs_ = rhs.phonetic_runs_;
|
||||
phonetic_properties_ = rhs.phonetic_properties_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -65,6 +67,8 @@ rich_text::rich_text(const rich_text_run &single_run)
|
|||
void rich_text::clear()
|
||||
{
|
||||
runs_.clear();
|
||||
phonetic_runs_.clear();
|
||||
phonetic_properties_.clear();
|
||||
}
|
||||
|
||||
void rich_text::plain_text(const std::string &s, bool preserve_space = false)
|
||||
|
@ -99,6 +103,36 @@ void rich_text::add_run(const rich_text_run &t)
|
|||
runs_.push_back(t);
|
||||
}
|
||||
|
||||
std::vector<phonetic_run> rich_text::phonetic_runs() const
|
||||
{
|
||||
return phonetic_runs_;
|
||||
}
|
||||
|
||||
void rich_text::phonetic_runs(const std::vector<phonetic_run> &new_phonetic_runs)
|
||||
{
|
||||
phonetic_runs_ = new_phonetic_runs;
|
||||
}
|
||||
|
||||
void rich_text::add_phonetic_run(const phonetic_run &r)
|
||||
{
|
||||
phonetic_runs_.push_back(r);
|
||||
}
|
||||
|
||||
bool rich_text::has_phonetic_properties() const
|
||||
{
|
||||
return phonetic_properties_.is_set();
|
||||
}
|
||||
|
||||
const phonetic_pr& rich_text::phonetic_properties() const
|
||||
{
|
||||
return phonetic_properties_.get();
|
||||
}
|
||||
|
||||
void rich_text::phonetic_properties(const phonetic_pr& phonetic_props)
|
||||
{
|
||||
phonetic_properties_.set(phonetic_props);
|
||||
}
|
||||
|
||||
bool rich_text::operator==(const rich_text &rhs) const
|
||||
{
|
||||
if (runs_.size() != rhs.runs_.size()) return false;
|
||||
|
@ -108,6 +142,15 @@ bool rich_text::operator==(const rich_text &rhs) const
|
|||
if (runs_[i] != rhs.runs_[i]) return false;
|
||||
}
|
||||
|
||||
if (phonetic_runs_.size() != rhs.phonetic_runs_.size()) return false;
|
||||
|
||||
for (std::size_t i = 0; i < phonetic_runs_.size(); i++)
|
||||
{
|
||||
if (phonetic_runs_[i] != rhs.phonetic_runs_[i]) return false;
|
||||
}
|
||||
|
||||
if (phonetic_properties_ != rhs.phonetic_properties_) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ cell_impl::cell_impl()
|
|||
column_(1),
|
||||
row_(1),
|
||||
is_merged_(false),
|
||||
phonetics_visible_(false),
|
||||
value_numeric_(0)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ struct cell_impl
|
|||
row_t row_;
|
||||
|
||||
bool is_merged_;
|
||||
bool phonetics_visible_;
|
||||
|
||||
rich_text value_text_;
|
||||
double value_numeric_;
|
||||
|
@ -72,6 +73,7 @@ inline bool operator==(const cell_impl &lhs, const cell_impl &rhs)
|
|||
&& lhs.column_ == rhs.column_
|
||||
&& lhs.row_ == rhs.row_
|
||||
&& lhs.is_merged_ == rhs.is_merged_
|
||||
&& lhs.phonetics_visible_ == rhs.phonetics_visible_
|
||||
&& lhs.value_text_ == rhs.value_text_
|
||||
&& lhs.value_numeric_ == rhs.value_numeric_
|
||||
&& lhs.formula_ == rhs.formula_
|
||||
|
|
|
@ -51,12 +51,7 @@ struct stylesheet
|
|||
|
||||
impl.parent = this;
|
||||
impl.id = format_impls.size() - 1;
|
||||
|
||||
impl.border_id = 0;
|
||||
impl.fill_id = 0;
|
||||
impl.font_id = 0;
|
||||
impl.number_format_id = 0;
|
||||
|
||||
|
||||
impl.references = default_format ? 1 : 0;
|
||||
|
||||
return xlnt::format(&impl);
|
||||
|
|
|
@ -1673,13 +1673,13 @@ std::string number_formatter::format_number(const format_code &format, double nu
|
|||
|
||||
case template_part::template_type::day_abbreviation:
|
||||
{
|
||||
result.append(day_names->at(static_cast<std::size_t>(dt.weekday()) - 1).substr(0, 3));
|
||||
result.append(day_names->at(static_cast<std::size_t>(dt.weekday())).substr(0, 3));
|
||||
break;
|
||||
}
|
||||
|
||||
case template_part::template_type::day_name:
|
||||
{
|
||||
result.append(day_names->at(static_cast<std::size_t>(dt.weekday()) - 1));
|
||||
result.append(day_names->at(static_cast<std::size_t>(dt.weekday())));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -221,9 +221,13 @@ cell xlsx_consumer::read_cell()
|
|||
row_properties.dy_descent = parser().attribute<double>(qn("x14ac", "dyDescent"));
|
||||
}
|
||||
|
||||
if (parser().attribute_present("spans")) {
|
||||
row_properties.spans = parser().attribute("spans");
|
||||
}
|
||||
|
||||
skip_attributes({"customFormat", "s", "customFont",
|
||||
"outlineLevel", "collapsed", "thickTop", "thickBot",
|
||||
"ph", "spans"});
|
||||
"ph"});
|
||||
}
|
||||
|
||||
if (!in_element(qn("spreadsheetml", "row")))
|
||||
|
@ -241,6 +245,11 @@ cell xlsx_consumer::read_cell()
|
|||
cell.d_->column_ = reference.column_index();
|
||||
cell.d_->row_ = reference.row();
|
||||
|
||||
if (parser().attribute_present("ph"))
|
||||
{
|
||||
cell.d_->phonetics_visible_ = parser().attribute<bool>("ph");
|
||||
}
|
||||
|
||||
auto has_type = parser().attribute_present("t");
|
||||
auto type = has_type ? parser().attribute("t") : "n";
|
||||
|
||||
|
@ -713,9 +722,13 @@ void xlsx_consumer::read_worksheet_sheetdata()
|
|||
row_properties.custom_format.set(parser().attribute<bool>("customFormat"));
|
||||
}
|
||||
|
||||
if (parser().attribute_present("spans")) {
|
||||
row_properties.spans = parser().attribute("spans");
|
||||
}
|
||||
|
||||
skip_attributes({"customFont",
|
||||
"outlineLevel", "collapsed", "thickTop", "thickBot",
|
||||
"ph", "spans"});
|
||||
"ph"});
|
||||
|
||||
while (in_element(qn("spreadsheetml", "row")))
|
||||
{
|
||||
|
@ -730,6 +743,11 @@ void xlsx_consumer::read_worksheet_sheetdata()
|
|||
cell.format(target_.format(static_cast<std::size_t>(std::stoull(parser().attribute("s")))));
|
||||
}
|
||||
|
||||
if (parser().attribute_present("ph"))
|
||||
{
|
||||
cell.d_->phonetics_visible_ = parser().attribute<bool>("ph");
|
||||
}
|
||||
|
||||
auto has_value = false;
|
||||
auto value_string = std::string();
|
||||
|
||||
|
@ -2339,7 +2357,7 @@ void xlsx_consumer::read_stylesheet()
|
|||
}
|
||||
record.first.border_id = parser().attribute_present("borderId")
|
||||
? parser().attribute<std::size_t>("borderId")
|
||||
: 0;
|
||||
: optional<std::size_t>();
|
||||
|
||||
if (parser().attribute_present("applyFill"))
|
||||
{
|
||||
|
@ -2347,7 +2365,7 @@ void xlsx_consumer::read_stylesheet()
|
|||
}
|
||||
record.first.fill_id = parser().attribute_present("fillId")
|
||||
? parser().attribute<std::size_t>("fillId")
|
||||
: 0;
|
||||
: optional<std::size_t>();
|
||||
|
||||
if (parser().attribute_present("applyFont"))
|
||||
{
|
||||
|
@ -2355,7 +2373,7 @@ void xlsx_consumer::read_stylesheet()
|
|||
}
|
||||
record.first.font_id = parser().attribute_present("fontId")
|
||||
? parser().attribute<std::size_t>("fontId")
|
||||
: 0;
|
||||
: optional<std::size_t>();
|
||||
|
||||
if (parser().attribute_present("applyNumberFormat"))
|
||||
{
|
||||
|
@ -2363,7 +2381,7 @@ void xlsx_consumer::read_stylesheet()
|
|||
}
|
||||
record.first.number_format_id = parser().attribute_present("numFmtId")
|
||||
? parser().attribute<std::size_t>("numFmtId")
|
||||
: 0;
|
||||
: optional<std::size_t>();
|
||||
|
||||
auto apply_alignment_present = parser().attribute_present("applyAlignment");
|
||||
if (apply_alignment_present)
|
||||
|
@ -2990,11 +3008,34 @@ rich_text xlsx_consumer::read_rich_text(const xml::qname &parent)
|
|||
}
|
||||
else if (text_element == xml::qname(xmlns, "rPh"))
|
||||
{
|
||||
skip_remaining_content(text_element);
|
||||
phonetic_run pr;
|
||||
pr.start = parser().attribute<std::uint32_t>("sb");
|
||||
pr.end = parser().attribute<std::uint32_t>("eb");
|
||||
|
||||
expect_start_element(xml::qname(xmlns, "t"), xml::content::simple);
|
||||
pr.text = read_text();
|
||||
|
||||
if (parser().attribute_present(xml_space))
|
||||
{
|
||||
pr.preserve_space = parser().attribute(xml_space) == "preserve";
|
||||
}
|
||||
|
||||
expect_end_element(xml::qname(xmlns, "t"));
|
||||
|
||||
t.add_phonetic_run(pr);
|
||||
}
|
||||
else if (text_element == xml::qname(xmlns, "phoneticPr"))
|
||||
{
|
||||
skip_remaining_content(text_element);
|
||||
phonetic_pr ph(parser().attribute<std::uint32_t>("fontId"));
|
||||
if (parser().attribute_present("type"))
|
||||
{
|
||||
ph.type(phonetic_pr::type_from_string(parser().attribute("type")));
|
||||
}
|
||||
if (parser().attribute_present("alignment"))
|
||||
{
|
||||
ph.alignment(phonetic_pr::alignment_from_string(parser().attribute("alignment")));
|
||||
}
|
||||
t.phonetic_properties(ph);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -2435,6 +2435,10 @@ void xlsx_producer::write_worksheet(const relationship &rel)
|
|||
// See note for CT_Row, span attribute about block optimization
|
||||
if (first_row_in_block)
|
||||
{
|
||||
// reset block column range
|
||||
first_block_column = constants::max_column();
|
||||
last_block_column = constants::min_column();
|
||||
|
||||
first_check_row = row;
|
||||
// round up to the next multiple of 16
|
||||
last_check_row = ((row / 16) + 1) * 16;
|
||||
|
@ -2444,8 +2448,8 @@ void xlsx_producer::write_worksheet(const relationship &rel)
|
|||
{
|
||||
for (auto column = dimension.top_left().column(); column <= dimension.bottom_right().column(); ++column)
|
||||
{
|
||||
if (!ws.has_cell(cell_reference(column, row))) continue;
|
||||
auto cell = ws.cell(cell_reference(column, row));
|
||||
if (!ws.has_cell(cell_reference(column, check_row))) continue;
|
||||
auto cell = ws.cell(cell_reference(column, check_row));
|
||||
if (cell.garbage_collectible()) continue;
|
||||
|
||||
first_block_column = std::min(first_block_column, cell.column());
|
||||
|
@ -2530,6 +2534,11 @@ void xlsx_producer::write_worksheet(const relationship &rel)
|
|||
|
||||
write_attribute("r", cell.reference().to_string());
|
||||
|
||||
if (cell.phonetics_visible())
|
||||
{
|
||||
write_attribute("ph", write_bool(true));
|
||||
}
|
||||
|
||||
if (cell.has_format())
|
||||
{
|
||||
write_attribute("s", cell.format().d_->id);
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#pragma clang diagnostic pop
|
||||
|
||||
#include <detail/unicode.hpp>
|
||||
#include <xlnt/utils/exceptions.hpp>
|
||||
|
||||
namespace xlnt {
|
||||
namespace detail {
|
||||
|
@ -69,5 +70,16 @@ std::string latin1_to_utf8(const std::string &latin1)
|
|||
return utf8;
|
||||
}
|
||||
|
||||
size_t string_length(const std::string &utf8_string)
|
||||
{
|
||||
auto end_it = utf8::find_invalid(utf8_string.begin(), utf8_string.end());
|
||||
if (end_it != utf8_string.end())
|
||||
{
|
||||
throw xlnt::exception("Invalid UTF-8 encoding detected");
|
||||
}
|
||||
|
||||
return utf8::distance(utf8_string.begin(), end_it);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace xlnt
|
||||
|
|
|
@ -29,6 +29,7 @@ namespace detail {
|
|||
std::u16string utf8_to_utf16(const std::string &utf8_string);
|
||||
std::string utf16_to_utf8(const std::u16string &utf16_string);
|
||||
std::string latin1_to_utf8(const std::string &latin1);
|
||||
size_t string_length(const std::string &utf8_string);
|
||||
|
||||
} // namespace detail
|
||||
} // namespace xlnt
|
||||
|
|
|
@ -126,18 +126,10 @@ date date::today()
|
|||
|
||||
int date::weekday() const
|
||||
{
|
||||
auto year_temp = (month == 1 || month == 2) ? year - 1 : year;
|
||||
auto month_temp = month == 1 ? 13 : month == 2 ? 14 : month;
|
||||
auto day_temp = day + 1;
|
||||
std::tm tm {0, 0, 0, day, month - 1, year - 1900};
|
||||
std::time_t time = std::mktime(&tm);
|
||||
|
||||
auto days = day_temp + static_cast<int>(13 * (month_temp + 1) / 5.0) + (year_temp % 100)
|
||||
+ static_cast<int>((year_temp % 100) / 4.0);
|
||||
auto gregorian = days + static_cast<int>(year_temp / 400.0) - 2 * year_temp / 100;
|
||||
auto julian = days + 5 - year_temp / 100;
|
||||
|
||||
int weekday = (year_temp > 1582 ? gregorian : julian) % 7;
|
||||
|
||||
return weekday == 0 ? 7 : weekday;
|
||||
return safe_localtime(time).tm_wday;
|
||||
}
|
||||
|
||||
} // namespace xlnt
|
||||
|
|
|
@ -1617,9 +1617,9 @@ struct rel_id_sorter
|
|||
bool operator()(const xlnt::relationship &lhs, const xlnt::relationship &rhs)
|
||||
{
|
||||
// format is rTd<decimal number 1..n>
|
||||
if (lhs.id().size() < rhs.id().size()) // a number with more digits will be larger
|
||||
if (lhs.id().size() != rhs.id().size()) // a number with more digits will be larger
|
||||
{
|
||||
return true;
|
||||
return lhs.id().size() < rhs.id().size();
|
||||
}
|
||||
return lhs.id() < rhs.id();
|
||||
}
|
||||
|
|
|
@ -42,10 +42,14 @@
|
|||
#include <xlnt/worksheet/range_iterator.hpp>
|
||||
#include <xlnt/worksheet/range_reference.hpp>
|
||||
#include <xlnt/worksheet/worksheet.hpp>
|
||||
#include <xlnt/worksheet/row_properties.hpp>
|
||||
#include <xlnt/worksheet/column_properties.hpp>
|
||||
#include <detail/constants.hpp>
|
||||
#include <detail/default_case.hpp>
|
||||
#include <detail/implementations/cell_impl.hpp>
|
||||
#include <detail/implementations/workbook_impl.hpp>
|
||||
#include <detail/implementations/worksheet_impl.hpp>
|
||||
#include <detail/unicode.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -234,7 +238,7 @@ void worksheet::title(const std::string &title)
|
|||
return;
|
||||
}
|
||||
// excel limits worksheet titles to 31 characters
|
||||
if (title.empty() || title.length() > 31)
|
||||
if (title.empty() || detail::string_length(title) > 31)
|
||||
{
|
||||
throw invalid_sheet_title(title);
|
||||
}
|
||||
|
@ -574,7 +578,7 @@ column_t worksheet::highest_column_or_props() const
|
|||
|
||||
range_reference worksheet::calculate_dimension() const
|
||||
{
|
||||
return range_reference(lowest_column(), lowest_row(),
|
||||
return range_reference(lowest_column(), lowest_row_or_props(),
|
||||
highest_column(), highest_row_or_props());
|
||||
}
|
||||
|
||||
|
@ -736,6 +740,182 @@ void worksheet::clear_row(row_t row)
|
|||
// TODO: garbage collect newly unreferenced resources such as styles?
|
||||
}
|
||||
|
||||
void worksheet::insert_rows(row_t row, std::uint32_t amount)
|
||||
{
|
||||
move_cells(row, amount, row_or_col_t::row);
|
||||
}
|
||||
|
||||
void worksheet::insert_columns(column_t column, std::uint32_t amount)
|
||||
{
|
||||
move_cells(column.index, amount, row_or_col_t::column);
|
||||
}
|
||||
|
||||
void worksheet::delete_rows(row_t row, std::uint32_t amount)
|
||||
{
|
||||
move_cells(row + amount, amount, row_or_col_t::row, true);
|
||||
}
|
||||
|
||||
void worksheet::delete_columns(column_t column, std::uint32_t amount)
|
||||
{
|
||||
move_cells(column.index + amount, amount, row_or_col_t::column, true);
|
||||
}
|
||||
|
||||
void worksheet::move_cells(std::uint32_t min_index, std::uint32_t amount, row_or_col_t row_or_col, bool reverse)
|
||||
{
|
||||
if (reverse && amount > min_index)
|
||||
{
|
||||
throw xlnt::invalid_parameter();
|
||||
}
|
||||
|
||||
if ((!reverse && row_or_col == row_or_col_t::row && min_index > constants::max_row() - amount) ||
|
||||
(!reverse && row_or_col == row_or_col_t::column && min_index > constants::max_column() - amount))
|
||||
{
|
||||
throw xlnt::exception("Cannot move cells as they would be outside the maximum bounds of the spreadsheet");
|
||||
}
|
||||
|
||||
std::vector<detail::cell_impl> cells_to_move;
|
||||
|
||||
auto cell_iter = d_->cell_map_.cbegin();
|
||||
while (cell_iter != d_->cell_map_.cend())
|
||||
{
|
||||
std::uint32_t current_index;
|
||||
switch (row_or_col)
|
||||
{
|
||||
case row_or_col_t::row:
|
||||
current_index = cell_iter->first.row();
|
||||
break;
|
||||
case row_or_col_t::column:
|
||||
current_index = cell_iter->first.column().index;
|
||||
break;
|
||||
default:
|
||||
throw xlnt::unhandled_switch_case();
|
||||
}
|
||||
|
||||
if (current_index >= min_index) // extract cells to be moved
|
||||
{
|
||||
auto cell = cell_iter->second;
|
||||
if (row_or_col == row_or_col_t::row)
|
||||
{
|
||||
cell.row_ = reverse ? cell.row_ - amount : cell.row_ + amount;
|
||||
}
|
||||
else if (row_or_col == row_or_col_t::column)
|
||||
{
|
||||
cell.column_ = reverse ? cell.column_.index - amount: cell.column_.index + amount;
|
||||
}
|
||||
|
||||
cells_to_move.push_back(cell);
|
||||
cell_iter = d_->cell_map_.erase(cell_iter);
|
||||
}
|
||||
else if (reverse && current_index >= min_index - amount) // delete destination cells
|
||||
{
|
||||
cell_iter = d_->cell_map_.erase(cell_iter);
|
||||
}
|
||||
else // skip other cells
|
||||
{
|
||||
++cell_iter;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &cell : cells_to_move)
|
||||
{
|
||||
d_->cell_map_[cell_reference(cell.column_, cell.row_)] = cell;
|
||||
}
|
||||
|
||||
if (row_or_col == row_or_col_t::row)
|
||||
{
|
||||
std::vector<std::pair<row_t, xlnt::row_properties>> properties_to_move;
|
||||
|
||||
auto row_prop_iter = d_->row_properties_.cbegin();
|
||||
while (row_prop_iter != d_->row_properties_.cend())
|
||||
{
|
||||
auto current_row = row_prop_iter->first;
|
||||
if (current_row >= min_index) // extract properties that need to be moved
|
||||
{
|
||||
auto tmp_row = reverse ? current_row - amount : current_row + amount;
|
||||
properties_to_move.push_back({tmp_row, row_prop_iter->second});
|
||||
row_prop_iter = d_->row_properties_.erase(row_prop_iter);
|
||||
}
|
||||
else if (reverse && current_row >= min_index - amount) // clear properties of destination when in reverse
|
||||
{
|
||||
row_prop_iter = d_->row_properties_.erase(row_prop_iter);
|
||||
}
|
||||
else // skip the rest
|
||||
{
|
||||
++row_prop_iter;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &prop : properties_to_move)
|
||||
{
|
||||
add_row_properties(prop.first, prop.second);
|
||||
}
|
||||
}
|
||||
else if (row_or_col == row_or_col_t::column)
|
||||
{
|
||||
std::vector<std::pair<column_t, xlnt::column_properties>> properties_to_move;
|
||||
|
||||
auto col_prop_iter = d_->column_properties_.cbegin();
|
||||
while (col_prop_iter != d_->column_properties_.cend())
|
||||
{
|
||||
auto current_col = col_prop_iter->first.index;
|
||||
if (current_col >= min_index) // extract properties that need to be moved
|
||||
{
|
||||
auto tmp_column = column_t(reverse ? current_col - amount : current_col + amount);
|
||||
properties_to_move.push_back({tmp_column, col_prop_iter->second});
|
||||
col_prop_iter = d_->column_properties_.erase(col_prop_iter);
|
||||
}
|
||||
else if (reverse && current_col >= min_index - amount) // clear properties of destination when in reverse
|
||||
{
|
||||
col_prop_iter = d_->column_properties_.erase(col_prop_iter);
|
||||
}
|
||||
else // skip the rest
|
||||
{
|
||||
++col_prop_iter;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &prop : properties_to_move)
|
||||
{
|
||||
add_column_properties(prop.first, prop.second);
|
||||
}
|
||||
}
|
||||
|
||||
// adjust merged cells
|
||||
auto shift_reference = [min_index, amount, row_or_col, reverse](cell_reference &ref)
|
||||
{
|
||||
auto index = row_or_col == row_or_col_t::row ?
|
||||
ref.row() :
|
||||
ref.column_index();
|
||||
if (index >= min_index)
|
||||
{
|
||||
auto new_index = reverse ? index - amount : index + amount;
|
||||
if (row_or_col == row_or_col_t::row)
|
||||
{
|
||||
ref.row(new_index);
|
||||
}
|
||||
else if (row_or_col == row_or_col_t::column)
|
||||
{
|
||||
ref.column_index(new_index);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (auto merged_cell = d_->merged_cells_.begin(); merged_cell != d_->merged_cells_.end(); ++merged_cell)
|
||||
{
|
||||
cell_reference new_top_left = merged_cell->top_left();
|
||||
shift_reference(new_top_left);
|
||||
|
||||
cell_reference new_bottom_right = merged_cell->bottom_right();
|
||||
shift_reference(new_bottom_right);
|
||||
|
||||
range_reference new_range {new_top_left, new_bottom_right};
|
||||
if (*merged_cell != new_range)
|
||||
{
|
||||
*merged_cell = new_range;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool worksheet::operator==(const worksheet &other) const
|
||||
{
|
||||
return compare(other, true);
|
||||
|
|
|
@ -82,6 +82,7 @@ public:
|
|||
register_test(test_hyperlink);
|
||||
register_test(test_comment);
|
||||
register_test(test_copy_and_compare);
|
||||
register_test(test_cell_phonetic_properties);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -687,7 +688,6 @@ private:
|
|||
|
||||
xlnt_assert(!cell.has_hyperlink());
|
||||
xlnt_assert_throws(cell.hyperlink(), xlnt::invalid_attribute);
|
||||
xlnt_assert_throws(cell.hyperlink("notaurl"), xlnt::invalid_parameter);
|
||||
xlnt_assert_throws(cell.hyperlink(""), xlnt::invalid_parameter);
|
||||
// link without optional display
|
||||
const std::string link1("http://example.com");
|
||||
|
@ -707,6 +707,13 @@ private:
|
|||
xlnt_assert_equals(cell.hyperlink().url(), link2);
|
||||
xlnt_assert_equals(cell.hyperlink().relationship().target().to_string(), link2);
|
||||
xlnt_assert_equals(cell.hyperlink().display(), display_txt);
|
||||
// relative (local) url
|
||||
const std::string local("../test_local");
|
||||
cell.hyperlink(local);
|
||||
xlnt_assert(cell.has_hyperlink());
|
||||
xlnt_assert(cell.hyperlink().external());
|
||||
xlnt_assert_equals(cell.hyperlink().url(), local);
|
||||
xlnt_assert_equals(cell.hyperlink().relationship().target().to_string(), local);
|
||||
// value
|
||||
int cell_test_val = 123;
|
||||
cell.value(cell_test_val);
|
||||
|
@ -805,6 +812,19 @@ private:
|
|||
cell3 = cell2;
|
||||
xlnt_assert_equals(cell2, cell3);
|
||||
}
|
||||
|
||||
void test_cell_phonetic_properties()
|
||||
{
|
||||
xlnt::workbook wb;
|
||||
auto ws = wb.active_sheet();
|
||||
auto cell1 = ws.cell("A1");
|
||||
|
||||
xlnt_assert_equals(cell1.phonetics_visible(), false);
|
||||
cell1.show_phonetics(true);
|
||||
xlnt_assert_equals(cell1.phonetics_visible(), true);
|
||||
cell1.show_phonetics(false);
|
||||
xlnt_assert_equals(cell1.phonetics_visible(), false);
|
||||
}
|
||||
};
|
||||
|
||||
static cell_test_suite x{};
|
|
@ -36,6 +36,8 @@ public:
|
|||
{
|
||||
register_test(test_operators);
|
||||
register_test(test_runs);
|
||||
register_test(test_phonetic_runs);
|
||||
register_test(test_phonetic_properties);
|
||||
}
|
||||
|
||||
void test_operators()
|
||||
|
@ -115,5 +117,30 @@ public:
|
|||
rt.runs(test_runs);
|
||||
xlnt_assert_equals(test_runs, rt.runs());
|
||||
}
|
||||
|
||||
void test_phonetic_runs()
|
||||
{
|
||||
xlnt::rich_text rt;
|
||||
rt.plain_text("取引", true);
|
||||
rt.add_phonetic_run({"トリヒキ", 0, 2});
|
||||
|
||||
xlnt_assert_equals(rt.phonetic_runs().size(), 1);
|
||||
xlnt_assert_equals(rt.phonetic_runs()[0].text, "トリヒキ");
|
||||
xlnt_assert_equals(rt.phonetic_runs()[0].start, 0);
|
||||
xlnt_assert_equals(rt.phonetic_runs()[0].end, 2);
|
||||
}
|
||||
|
||||
void test_phonetic_properties()
|
||||
{
|
||||
xlnt::rich_text rt;
|
||||
xlnt::phonetic_pr ph(1);
|
||||
ph.type(xlnt::phonetic_pr::type_from_string("fullwidthKatakana"));
|
||||
ph.alignment(xlnt::phonetic_pr::alignment_from_string("Center"));
|
||||
rt.phonetic_properties(ph);
|
||||
|
||||
xlnt_assert_equals(rt.has_phonetic_properties(), true);
|
||||
xlnt_assert_equals(rt.phonetic_properties().has_type(), true);
|
||||
xlnt_assert_equals(rt.phonetic_properties().has_alignment(), true);
|
||||
}
|
||||
};
|
||||
static rich_text_test_suite x{};
|
Binary file not shown.
BIN
tests/data/15_phonetics.xlsx
Normal file
BIN
tests/data/15_phonetics.xlsx
Normal file
Binary file not shown.
BIN
tests/data/Issue353_first_row_empty_w_properties.xlsx
Normal file
BIN
tests/data/Issue353_first_row_empty_w_properties.xlsx
Normal file
Binary file not shown.
|
@ -37,12 +37,12 @@ public:
|
|||
{
|
||||
register_test(test_basic);
|
||||
register_test(test_simple_format);
|
||||
register_test(test_bad_date_format);
|
||||
register_test(test_simple_date);
|
||||
register_test(test_short_month);
|
||||
register_test(test_month_abbreviation);
|
||||
register_test(test_month_name);
|
||||
register_test(test_basic);
|
||||
register_test(test_simple_format);
|
||||
register_test(test_upper_case_date);
|
||||
register_test(test_simple_date);
|
||||
register_test(test_short_day);
|
||||
|
@ -155,6 +155,46 @@ public:
|
|||
xlnt_assert_equals(formatted, "zero0");
|
||||
}
|
||||
|
||||
void test_bad_date_format()
|
||||
{
|
||||
auto date = xlnt::date(2016, 6, 18);
|
||||
auto date_number = date.to_number(xlnt::calendar::windows_1900);
|
||||
|
||||
xlnt::number_format nf;
|
||||
|
||||
nf.format_string("[x]");
|
||||
xlnt_assert_throws(nf.format(date_number, xlnt::calendar::windows_1900),
|
||||
std::runtime_error);
|
||||
|
||||
nf.format_string("mmmmmm");
|
||||
xlnt_assert_throws(nf.format(date_number, xlnt::calendar::windows_1900),
|
||||
std::runtime_error);
|
||||
|
||||
nf.format_string("ddddd");
|
||||
xlnt_assert_throws(nf.format(date_number, xlnt::calendar::windows_1900),
|
||||
std::runtime_error);
|
||||
|
||||
nf.format_string("yyy");
|
||||
xlnt_assert_throws(nf.format(date_number, xlnt::calendar::windows_1900),
|
||||
std::runtime_error);
|
||||
|
||||
nf.format_string("hhh");
|
||||
xlnt_assert_throws(nf.format(date_number, xlnt::calendar::windows_1900),
|
||||
std::runtime_error);
|
||||
|
||||
nf.format_string("sss");
|
||||
xlnt_assert_throws(nf.format(date_number, xlnt::calendar::windows_1900),
|
||||
std::runtime_error);
|
||||
|
||||
nf.format_string("AA");
|
||||
xlnt_assert_throws(nf.format(date_number, xlnt::calendar::windows_1900),
|
||||
std::runtime_error);
|
||||
|
||||
nf.format_string("q");
|
||||
xlnt_assert_throws(nf.format(date_number, xlnt::calendar::windows_1900),
|
||||
std::runtime_error);
|
||||
}
|
||||
|
||||
void test_upper_case_date()
|
||||
{
|
||||
auto date = xlnt::date(2016, 6, 18);
|
||||
|
@ -259,7 +299,7 @@ public:
|
|||
nf.format_string("dddd");
|
||||
auto formatted = nf.format(date_number, xlnt::calendar::windows_1900);
|
||||
|
||||
xlnt_assert_equals(formatted, "Sunday");
|
||||
xlnt_assert_equals(formatted, "Saturday");
|
||||
}
|
||||
|
||||
void test_day_abbreviation()
|
||||
|
@ -271,7 +311,7 @@ public:
|
|||
nf.format_string("ddd");
|
||||
auto formatted = nf.format(date_number, xlnt::calendar::windows_1900);
|
||||
|
||||
xlnt_assert_equals(formatted, "Sun");
|
||||
xlnt_assert_equals(formatted, "Sat");
|
||||
}
|
||||
|
||||
void test_month_letter()
|
||||
|
|
|
@ -40,6 +40,7 @@ public:
|
|||
register_test(test_early_date);
|
||||
register_test(test_mac_calendar);
|
||||
register_test(test_operators);
|
||||
register_test(test_weekday);
|
||||
}
|
||||
|
||||
void test_from_string()
|
||||
|
@ -113,5 +114,12 @@ public:
|
|||
xlnt_assert_equals(d1, d2);
|
||||
xlnt_assert_differs(d1, d3);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
static datetime_test_suite x;
|
|
@ -67,6 +67,7 @@ public:
|
|||
register_test(test_id_gen);
|
||||
register_test(test_load_file);
|
||||
register_test(test_Issue279);
|
||||
register_test(test_Issue353);
|
||||
}
|
||||
|
||||
void test_active_sheet()
|
||||
|
@ -495,5 +496,18 @@ public:
|
|||
//save a copy file
|
||||
wb.save("temp.xlsx");
|
||||
}
|
||||
|
||||
void test_Issue353()
|
||||
{
|
||||
xlnt::workbook wb1;
|
||||
wb1.load(path_helper::test_file("Issue353_first_row_empty_w_properties.xlsx"));
|
||||
wb1.save("temp_issue353.xlsx");
|
||||
|
||||
xlnt::workbook wb2;
|
||||
wb2.load("temp_issue353.xlsx");
|
||||
auto ws = wb2.active_sheet();
|
||||
xlnt_assert_equals(ws.row_properties(1).spans.get(), "1:8");
|
||||
xlnt_assert_equals(ws.row_properties(17).spans.get(), "2:7");
|
||||
}
|
||||
};
|
||||
static workbook_test_suite x;
|
|
@ -104,6 +104,14 @@ public:
|
|||
register_test(test_clear_cell);
|
||||
register_test(test_clear_row);
|
||||
register_test(test_set_title);
|
||||
register_test(test_set_title_unicode);
|
||||
register_test(test_phonetics);
|
||||
register_test(test_insert_rows);
|
||||
register_test(test_insert_columns);
|
||||
register_test(test_delete_rows);
|
||||
register_test(test_delete_columns);
|
||||
register_test(test_insert_too_many);
|
||||
register_test(test_insert_delete_moves_merges);
|
||||
}
|
||||
|
||||
void test_new_worksheet()
|
||||
|
@ -1259,5 +1267,320 @@ public:
|
|||
xlnt_assert(ws1_title == ws1.title());
|
||||
xlnt_assert(ws2_title == ws2.title());
|
||||
}
|
||||
|
||||
void test_set_title_unicode()
|
||||
{
|
||||
xlnt::workbook wb;
|
||||
auto ws = wb.active_sheet();
|
||||
// the 31 char limit also applies to 4-byte characters
|
||||
const std::string test_long_utf8_title("巧みな外交は戦争を避ける助けとなる。");
|
||||
xlnt_assert_throws_nothing(ws.title(test_long_utf8_title));
|
||||
const std::string invalid_unicode("\xe6\x97\xa5\xd1\x88\xfa");
|
||||
xlnt_assert_throws(ws.title(invalid_unicode),
|
||||
xlnt::exception);
|
||||
}
|
||||
|
||||
void test_phonetics()
|
||||
{
|
||||
xlnt::workbook wb;
|
||||
wb.load(path_helper::test_file("15_phonetics.xlsx"));
|
||||
auto ws = wb.active_sheet();
|
||||
|
||||
xlnt_assert_equals(ws.cell("A1").phonetics_visible(), true);
|
||||
xlnt_assert_equals(ws.cell("A1").value<xlnt::rich_text>().phonetic_runs()[0].text, "シュウ ");
|
||||
xlnt_assert_equals(ws.cell("B1").phonetics_visible(), true);
|
||||
xlnt_assert_equals(ws.cell("C1").phonetics_visible(), false);
|
||||
|
||||
wb.save("temp.xlsx");
|
||||
|
||||
xlnt::workbook wb2;
|
||||
wb2.load("temp.xlsx");
|
||||
auto ws2 = wb2.active_sheet();
|
||||
|
||||
xlnt_assert_equals(ws2.cell("A1").phonetics_visible(), true);
|
||||
xlnt_assert_equals(ws2.cell("A1").value<xlnt::rich_text>().phonetic_runs()[0].text, "シュウ ");
|
||||
xlnt_assert_equals(ws2.cell("B1").phonetics_visible(), true);
|
||||
xlnt_assert_equals(ws2.cell("C1").phonetics_visible(), false);
|
||||
}
|
||||
|
||||
void test_insert_rows()
|
||||
{
|
||||
xlnt::workbook wb;
|
||||
auto ws = wb.active_sheet();
|
||||
|
||||
// set up a 2x2 grid
|
||||
ws.cell("A1").value("A1");
|
||||
ws.cell("A2").value("A2");
|
||||
ws.cell("B1").value("B1");
|
||||
ws.cell("B2").value("B2");
|
||||
|
||||
xlnt::row_properties row_prop;
|
||||
row_prop.height = 1;
|
||||
ws.add_row_properties(1, row_prop);
|
||||
row_prop.height = 2;
|
||||
ws.add_row_properties(2, row_prop);
|
||||
|
||||
xlnt::column_properties col_prop;
|
||||
col_prop.width = 1;
|
||||
ws.add_column_properties(1, col_prop);
|
||||
col_prop.width = 2;
|
||||
ws.add_column_properties(2, col_prop);
|
||||
|
||||
// insert
|
||||
ws.insert_rows(2, 2);
|
||||
|
||||
// first row should be unchanged
|
||||
xlnt_assert(ws.cell("A1").has_value());
|
||||
xlnt_assert(ws.cell("B1").has_value());
|
||||
xlnt_assert_equals(ws.cell("A1").value<std::string>(), "A1");
|
||||
xlnt_assert_equals(ws.cell("B1").value<std::string>(), "B1");
|
||||
xlnt_assert_equals(ws.row_properties(1).height, 1);
|
||||
|
||||
// second and third rows should be empty
|
||||
xlnt_assert(!ws.cell("A2").has_value());
|
||||
xlnt_assert(!ws.cell("B2").has_value());
|
||||
xlnt_assert(!ws.has_row_properties(2));
|
||||
xlnt_assert(!ws.cell("A3").has_value());
|
||||
xlnt_assert(!ws.cell("B3").has_value());
|
||||
xlnt_assert(!ws.has_row_properties(3));
|
||||
|
||||
// fourth row should have the contents and properties of the second
|
||||
xlnt_assert(ws.cell("A4").has_value());
|
||||
xlnt_assert(ws.cell("B4").has_value());
|
||||
xlnt_assert_equals(ws.cell("A4").value<std::string>(), "A2");
|
||||
xlnt_assert_equals(ws.cell("B4").value<std::string>(), "B2");
|
||||
xlnt_assert_equals(ws.row_properties(4).height, 2);
|
||||
|
||||
// column properties should remain unchanged
|
||||
xlnt_assert(ws.has_column_properties(1));
|
||||
xlnt_assert(ws.has_column_properties(2));
|
||||
}
|
||||
|
||||
void test_insert_columns()
|
||||
{
|
||||
xlnt::workbook wb;
|
||||
auto ws = wb.active_sheet();
|
||||
|
||||
// set up a 2x2 grid
|
||||
ws.cell("A1").value("A1");
|
||||
ws.cell("A2").value("A2");
|
||||
ws.cell("B1").value("B1");
|
||||
ws.cell("B2").value("B2");
|
||||
|
||||
xlnt::row_properties row_prop;
|
||||
row_prop.height = 1;
|
||||
ws.add_row_properties(1, row_prop);
|
||||
row_prop.height = 2;
|
||||
ws.add_row_properties(2, row_prop);
|
||||
|
||||
xlnt::column_properties col_prop;
|
||||
col_prop.width = 1;
|
||||
ws.add_column_properties(1, col_prop);
|
||||
col_prop.width = 2;
|
||||
ws.add_column_properties(2, col_prop);
|
||||
|
||||
// insert
|
||||
ws.insert_columns(2, 2);
|
||||
|
||||
// first column should be unchanged
|
||||
xlnt_assert(ws.cell("A1").has_value());
|
||||
xlnt_assert(ws.cell("A2").has_value());
|
||||
xlnt_assert_equals(ws.cell("A1").value<std::string>(), "A1");
|
||||
xlnt_assert_equals(ws.cell("A2").value<std::string>(), "A2");
|
||||
xlnt_assert_equals(ws.column_properties(1).width, 1);
|
||||
|
||||
// second and third columns should be empty
|
||||
xlnt_assert(!ws.cell("B1").has_value());
|
||||
xlnt_assert(!ws.cell("B2").has_value());
|
||||
xlnt_assert(!ws.has_column_properties(2));
|
||||
xlnt_assert(!ws.cell("C1").has_value());
|
||||
xlnt_assert(!ws.cell("C2").has_value());
|
||||
xlnt_assert(!ws.has_column_properties(3));
|
||||
|
||||
// fourth column should have the contents and properties of the second
|
||||
xlnt_assert(ws.cell("D1").has_value());
|
||||
xlnt_assert(ws.cell("D2").has_value());
|
||||
xlnt_assert_equals(ws.cell("D1").value<std::string>(), "B1");
|
||||
xlnt_assert_equals(ws.cell("D2").value<std::string>(), "B2");
|
||||
xlnt_assert_equals(ws.column_properties(4).width, 2);
|
||||
|
||||
// row properties should remain unchanged
|
||||
xlnt_assert_equals(ws.row_properties(1).height, 1);
|
||||
xlnt_assert_equals(ws.row_properties(2).height, 2);
|
||||
}
|
||||
|
||||
void test_delete_rows()
|
||||
{
|
||||
xlnt::workbook wb;
|
||||
auto ws = wb.active_sheet();
|
||||
|
||||
// set up a 4x4 grid
|
||||
for (int i = 1; i <= 4; ++i)
|
||||
{
|
||||
for (int j = 1; j <= 4; ++j)
|
||||
{
|
||||
ws.cell(xlnt::cell_reference(i, j)).value(xlnt::cell_reference(i, j).to_string());
|
||||
}
|
||||
|
||||
xlnt::row_properties row_prop;
|
||||
row_prop.height = i;
|
||||
ws.add_row_properties(i, row_prop);
|
||||
|
||||
xlnt::column_properties col_prop;
|
||||
col_prop.width = i;
|
||||
ws.add_column_properties(i, col_prop);
|
||||
}
|
||||
|
||||
// delete
|
||||
ws.delete_rows(2, 2);
|
||||
|
||||
// first row should remain unchanged
|
||||
xlnt_assert_equals(ws.cell("A1").value<std::string>(), "A1");
|
||||
xlnt_assert_equals(ws.cell("B1").value<std::string>(), "B1");
|
||||
xlnt_assert_equals(ws.cell("C1").value<std::string>(), "C1");
|
||||
xlnt_assert_equals(ws.cell("D1").value<std::string>(), "D1");
|
||||
xlnt_assert(ws.has_row_properties(1));
|
||||
xlnt_assert_equals(ws.row_properties(1).height, 1);
|
||||
|
||||
// second row should have the contents and properties of the fourth
|
||||
xlnt_assert_equals(ws.cell("A2").value<std::string>(), "A4");
|
||||
xlnt_assert_equals(ws.cell("B2").value<std::string>(), "B4");
|
||||
xlnt_assert_equals(ws.cell("C2").value<std::string>(), "C4");
|
||||
xlnt_assert_equals(ws.cell("D2").value<std::string>(), "D4");
|
||||
xlnt_assert(ws.has_row_properties(2));
|
||||
xlnt_assert_equals(ws.row_properties(2).height, 4);
|
||||
|
||||
// third and fourth rows should be empty
|
||||
auto empty_range = ws.range("A3:D4");
|
||||
for (auto empty_row : empty_range)
|
||||
{
|
||||
for (auto empty_cell : empty_row)
|
||||
{
|
||||
xlnt_assert(!empty_cell.has_value());
|
||||
}
|
||||
}
|
||||
xlnt_assert(!ws.has_row_properties(3));
|
||||
xlnt_assert(!ws.has_row_properties(4));
|
||||
|
||||
// column properties should remain unchanged
|
||||
for (int i = 1; i <= 4; ++i)
|
||||
{
|
||||
xlnt_assert(ws.has_column_properties(i));
|
||||
xlnt_assert_equals(ws.column_properties(i).width, i);
|
||||
}
|
||||
}
|
||||
|
||||
void test_delete_columns()
|
||||
{
|
||||
xlnt::workbook wb;
|
||||
auto ws = wb.active_sheet();
|
||||
|
||||
// set up a 4x4 grid
|
||||
for (int i = 1; i <= 4; ++i)
|
||||
{
|
||||
for (int j = 1; j <= 4; ++j)
|
||||
{
|
||||
ws.cell(xlnt::cell_reference(i, j)).value(xlnt::cell_reference(i, j).to_string());
|
||||
}
|
||||
|
||||
xlnt::row_properties row_prop;
|
||||
row_prop.height = i;
|
||||
ws.add_row_properties(i, row_prop);
|
||||
|
||||
xlnt::column_properties col_prop;
|
||||
col_prop.width = i;
|
||||
ws.add_column_properties(i, col_prop);
|
||||
}
|
||||
|
||||
// delete
|
||||
ws.delete_columns(2, 2);
|
||||
|
||||
// first column should remain unchanged
|
||||
xlnt_assert_equals(ws.cell("A1").value<std::string>(), "A1");
|
||||
xlnt_assert_equals(ws.cell("A2").value<std::string>(), "A2");
|
||||
xlnt_assert_equals(ws.cell("A3").value<std::string>(), "A3");
|
||||
xlnt_assert_equals(ws.cell("A4").value<std::string>(), "A4");
|
||||
xlnt_assert(ws.has_column_properties("A"));
|
||||
xlnt_assert_equals(ws.column_properties("A").width.get(), 1);
|
||||
|
||||
// second column should have the contents and properties of the fourth
|
||||
xlnt_assert_equals(ws.cell("B1").value<std::string>(), "D1");
|
||||
xlnt_assert_equals(ws.cell("B2").value<std::string>(), "D2");
|
||||
xlnt_assert_equals(ws.cell("B3").value<std::string>(), "D3");
|
||||
xlnt_assert_equals(ws.cell("B4").value<std::string>(), "D4");
|
||||
xlnt_assert(ws.has_column_properties("B"));
|
||||
xlnt_assert_equals(ws.column_properties("B").width.get(), 4);
|
||||
|
||||
// third and fourth columns should be empty
|
||||
auto empty_range = ws.range("C1:D4");
|
||||
for (auto empty_row : empty_range)
|
||||
{
|
||||
for (auto empty_cell : empty_row)
|
||||
{
|
||||
xlnt_assert(!empty_cell.has_value());
|
||||
}
|
||||
}
|
||||
xlnt_assert(!ws.has_column_properties("C"));
|
||||
xlnt_assert(!ws.has_column_properties("D"));
|
||||
|
||||
// row properties should remain unchanged
|
||||
for (int i = 1; i <= 4; ++i)
|
||||
{
|
||||
xlnt_assert(ws.has_row_properties(i));
|
||||
xlnt_assert_equals(ws.row_properties(i).height, i);
|
||||
}
|
||||
}
|
||||
|
||||
void test_insert_too_many()
|
||||
{
|
||||
xlnt::workbook wb;
|
||||
auto ws = wb.active_sheet();
|
||||
xlnt_assert_throws(ws.insert_rows(10, 4294967290),
|
||||
xlnt::exception);
|
||||
}
|
||||
|
||||
void test_insert_delete_moves_merges()
|
||||
{
|
||||
xlnt::workbook wb;
|
||||
auto ws = wb.active_sheet();
|
||||
ws.merge_cells("A1:A2");
|
||||
ws.merge_cells("B2:B3");
|
||||
ws.merge_cells("C3:C4");
|
||||
ws.merge_cells("A5:B5");
|
||||
ws.merge_cells("B6:C6");
|
||||
ws.merge_cells("C7:D7");
|
||||
|
||||
{
|
||||
ws.insert_rows(3, 3);
|
||||
ws.insert_columns(3, 3);
|
||||
auto merged = ws.merged_ranges();
|
||||
std::vector<xlnt::range_reference> expected =
|
||||
{
|
||||
xlnt::range_reference { "A1:A2" }, // stays
|
||||
xlnt::range_reference { "B2:B6" }, // expands
|
||||
xlnt::range_reference { "F6:F7" }, // shifts
|
||||
xlnt::range_reference { "A8:B8" }, // stays (shifted down)
|
||||
xlnt::range_reference { "B9:F9" }, // expands (shifted down)
|
||||
xlnt::range_reference { "F10:G10" }, // shifts (shifted down)
|
||||
};
|
||||
xlnt_assert_equals(merged, expected);
|
||||
}
|
||||
|
||||
{
|
||||
ws.delete_rows(4, 2);
|
||||
ws.delete_columns(4, 2);
|
||||
auto merged = ws.merged_ranges();
|
||||
std::vector<xlnt::range_reference> expected =
|
||||
{
|
||||
xlnt::range_reference { "A1:A2" }, // stays
|
||||
xlnt::range_reference { "B2:B4" }, // expands
|
||||
xlnt::range_reference { "D4:D5" }, // shifts
|
||||
xlnt::range_reference { "A6:B6" }, // stays (shifted down)
|
||||
xlnt::range_reference { "B7:D7" }, // expands (shifted down)
|
||||
xlnt::range_reference { "D8:E8" }, // shifts (shifted down)
|
||||
};
|
||||
xlnt_assert_equals(merged, expected);
|
||||
}
|
||||
}
|
||||
};
|
||||
static worksheet_test_suite x;
|
||||
static worksheet_test_suite x;
|
||||
|
|
Loading…
Reference in New Issue
Block a user