From 7316e2184cae010774e2a8c334b62f4f823f8d12 Mon Sep 17 00:00:00 2001 From: Thomas Fussell Date: Tue, 13 Oct 2015 16:35:22 -0400 Subject: [PATCH] replace pugiconfig.hpp with local header, fix some constness, dry up code --- include/xlnt/cell/cell.hpp | 19 ++- include/xlnt/styles/style.hpp | 16 +- include/xlnt/worksheet/worksheet.hpp | 2 + source/cell.cpp | 211 +++------------------------ source/detail/cell_impl.hpp | 178 ++++++++++++++++++++++ source/detail/include_pugixml.hpp | 8 + source/style.cpp | 12 +- source/workbook.cpp | 29 +++- source/worksheet.cpp | 55 ++++--- tests/test_cell.hpp | 2 +- tests/test_worksheet.hpp | 10 +- 11 files changed, 309 insertions(+), 233 deletions(-) create mode 100644 source/detail/include_pugixml.hpp diff --git a/include/xlnt/cell/cell.hpp b/include/xlnt/cell/cell.hpp index 864993a7..30f5f63e 100644 --- a/include/xlnt/cell/cell.hpp +++ b/include/xlnt/cell/cell.hpp @@ -92,6 +92,7 @@ public: // characteristics bool garbage_collectible() const; bool is_date() const; + std::size_t get_xf_index() const; // position cell_reference get_reference() const; @@ -110,16 +111,24 @@ public: style &get_style(); const style &get_style() const; void set_style(const style &s); + + // style shortcuts + std::string get_number_format(); std::string get_number_format() const; void set_number_format(const std::string &format_code); - std::size_t get_xf_index() const; - font get_font() const; + font &get_font(); + const font &get_font() const; fill &get_fill(); const fill &get_fill() const; - border get_border() const; - alignment get_alignment() const; - protection get_protection() const; + border &get_border(); + const border &get_border() const; + alignment &get_alignment(); + const alignment &get_alignment() const; + protection &get_protection(); + const protection &get_protection() const; + bool pivot_button(); bool pivot_button() const; + bool quote_prefix(); bool quote_prefix() const; // comment diff --git a/include/xlnt/styles/style.hpp b/include/xlnt/styles/style.hpp index 97494051..c4691036 100644 --- a/include/xlnt/styles/style.hpp +++ b/include/xlnt/styles/style.hpp @@ -41,30 +41,34 @@ public: style copy() const; - font get_font() const; + font &get_font(); + const font &get_font() const; void set_font(font font); fill &get_fill(); const fill &get_fill() const; void set_fill(fill &fill); - border get_border() const; + border &get_border(); + const border &get_border() const; void set_border(border borders); - alignment get_alignment() const; + alignment &get_alignment(); + const alignment get_alignment() const; void set_alignment(alignment alignment); number_format &get_number_format() { return number_format_; } const number_format &get_number_format() const { return number_format_; } void set_number_format(number_format number_format); - protection get_protection() const; + protection &get_protection(); + const protection &get_protection() const; void set_protection(protection protection); - bool pivot_button() const; + bool pivot_button(); void set_pivot_button(bool pivot); - bool quote_prefix() const; + bool quote_prefix(); void set_quote_prefix(bool quote); private: diff --git a/include/xlnt/worksheet/worksheet.hpp b/include/xlnt/worksheet/worksheet.hpp index 8a6a8840..1ca9ba86 100644 --- a/include/xlnt/worksheet/worksheet.hpp +++ b/include/xlnt/worksheet/worksheet.hpp @@ -278,6 +278,7 @@ public: // extents row_t get_lowest_row() const; row_t get_highest_row() const; + row_t get_next_row() const; column_t get_lowest_column() const; column_t get_highest_column() const; range_reference calculate_dimension() const; @@ -297,6 +298,7 @@ public: std::vector get_merged_ranges() const; // append + void append(); void append(const std::vector &cells); void append(const std::vector &cells); void append(const std::vector &cells); diff --git a/source/cell.cpp b/source/cell.cpp index e5c87674..6ee2a683 100644 --- a/source/cell.cpp +++ b/source/cell.cpp @@ -16,102 +16,6 @@ #include "detail/cell_impl.hpp" #include "detail/comment_impl.hpp" -namespace { - -// return s after checking encoding, size, and illegal characters -std::string check_string(std::string s) -{ - if (s.size() == 0) - { - return s; - } - - // check encoding? - - if (s.size() > 32767) - { - s = s.substr(0, 32767); // max string length in Excel - } - - for (unsigned char c : s) - { - if (c <= 8 || c == 11 || c == 12 || (c >= 14 && c <= 31)) - { - throw xlnt::illegal_character_error(static_cast(c)); - } - } - - return s; -} - -std::pair cast_numeric(const std::string &s) -{ - const char *str = s.c_str(); - char *str_end = nullptr; - auto result = std::strtold(str, &str_end); - if (str_end != str + s.size()) return{ false, 0 }; - return{ true, result }; -} - -std::pair cast_percentage(const std::string &s) -{ - if (s.back() == '%') - { - auto number = cast_numeric(s.substr(0, s.size() - 1)); - - if (number.first) - { - return{ true, number.second / 100 }; - } - } - - return { false, 0 }; -} - -std::pair cast_time(const std::string &s) -{ - xlnt::time result; - - try - { - auto last_colon = s.find_last_of(':'); - if (last_colon == std::string::npos) return { false, result }; - double seconds = std::stod(s.substr(last_colon + 1)); - result.second = static_cast(seconds); - result.microsecond = static_cast((seconds - static_cast(result.second)) * 1e6); - - auto first_colon = s.find_first_of(':'); - - if (first_colon == last_colon) - { - auto decimal_pos = s.find('.'); - if (decimal_pos != std::string::npos) - { - result.minute = std::stoi(s.substr(0, first_colon)); - } - else - { - result.hour = std::stoi(s.substr(0, first_colon)); - result.minute = result.second; - result.second = 0; - } - } - else - { - result.hour = std::stoi(s.substr(0, first_colon)); - result.minute = std::stoi(s.substr(first_colon + 1, last_colon - first_colon - 1)); - } - } - catch (std::invalid_argument) - { - return{ false, result }; - } - - return { true, result }; -} - -} // namespace - namespace xlnt { const xlnt::color xlnt::color::black(0); @@ -278,7 +182,7 @@ void cell::set_value(long double d) template<> void cell::set_value(std::string s) { - set_value_guess_type(s); + d_->set_string(s, get_parent().get_parent().get_guess_types()); } template<> @@ -286,101 +190,39 @@ void cell::set_value(char const *c) { set_value(std::string(c)); } + +template<> +void cell::set_value(cell c) +{ +} template<> void cell::set_value(date d) { - d_->is_date_ = true; - auto code = xlnt::number_format::format::date_yyyymmdd2; - auto number_format = xlnt::number_format(code); - get_style().set_number_format(number_format); auto base_date = get_parent().get_parent().get_properties().excel_base_date; - d_->value_numeric_ = d.to_number(base_date); - d_->type_ = type::numeric; + d_->set_date(d.to_number(base_date), xlnt::number_format::format::date_yyyymmdd2); } template<> void cell::set_value(datetime d) { - d_->is_date_ = true; - auto code = xlnt::number_format::format::date_datetime; - auto number_format = xlnt::number_format(code); - get_style().set_number_format(number_format); auto base_date = get_parent().get_parent().get_properties().excel_base_date; - d_->value_numeric_ = d.to_number(base_date); - d_->type_ = type::numeric; + d_->set_date(d.to_number(base_date), xlnt::number_format::format::date_datetime); } template<> void cell::set_value(time t) { - d_->is_date_ = true; - auto code = xlnt::number_format::format::date_time6; - auto number_format = xlnt::number_format(code); - get_style().set_number_format(number_format); - d_->value_numeric_ = t.to_number(); - d_->type_ = type::numeric; + d_->set_date(t.to_number(), xlnt::number_format::format::date_time6); } template<> void cell::set_value(timedelta t) { - auto code = xlnt::number_format::format::date_timedelta; - auto number_format = xlnt::number_format(code); - get_style().set_number_format(number_format); - d_->value_numeric_ = t.to_number(); - d_->type_ = type::numeric; + d_->set_date(t.to_number(), xlnt::number_format::format::date_timedelta); + d_->is_date_ = false; // a timedelta isn't actually a date, still uses mostly the same code } -void cell::set_value_guess_type(const std::string &s) -{ - d_->is_date_ = false; - auto temp = check_string(s); - d_->value_string_ = temp; - d_->type_ = type::string; - - if (temp.size() > 1 && temp.front() == '=') - { - d_->formula_ = temp; - d_->type_ = type::formula; - d_->value_string_.clear(); - } - else if(ErrorCodes.find(s) != ErrorCodes.end()) - { - d_->value_string_ = s; - d_->type_ = type::error; - } - else if(get_parent().get_parent().get_guess_types()) - { - auto percentage = cast_percentage(s); - - if (percentage.first) - { - d_->value_numeric_ = percentage.second; - d_->type_ = type::numeric; - get_style().get_number_format().set_format_code(xlnt::number_format::format::percentage); - } - else - { - auto time = cast_time(s); - - if (time.first) - { - set_value(time.second); - } - else - { - auto numeric = cast_numeric(s); - - if (numeric.first) - { - set_value(numeric.second); - } - } - } - } -} - bool cell::has_style() const { return d_->style_ != nullptr; @@ -428,22 +270,12 @@ bool cell::operator==(const cell &comparand) const style &cell::get_style() { - if(d_->style_ == nullptr) - { - d_->style_.reset(new style()); - } - - return *d_->style_; + return d_->get_style(true); } const style &cell::get_style() const { - if(d_->style_ == nullptr) - { - d_->style_.reset(new style()); - } - - return *d_->style_; + return d_->get_style(); } void cell::set_style(const xlnt::style &s) @@ -662,12 +494,17 @@ std::size_t cell::get_xf_index() const return d_->xf_index_; } +std::string cell::get_number_format() +{ + return get_style().get_number_format().get_format_code_string(); +} + std::string cell::get_number_format() const { return get_style().get_number_format().get_format_code_string(); } -font cell::get_font() const +font &cell::get_font() { return get_style().get_font(); } @@ -682,27 +519,27 @@ const fill &cell::get_fill() const return get_style().get_fill(); } -border cell::get_border() const +border &cell::get_border() { return get_style().get_border(); } -alignment cell::get_alignment() const +alignment &cell::get_alignment() { return get_style().get_alignment(); } -protection cell::get_protection() const +protection &cell::get_protection() { return get_style().get_protection(); } -bool cell::pivot_button() const +bool cell::pivot_button() { return get_style().pivot_button(); } -bool cell::quote_prefix() const +bool cell::quote_prefix() { return get_style().quote_prefix(); } diff --git a/source/detail/cell_impl.hpp b/source/detail/cell_impl.hpp index 4eeed53a..be3ad9ba 100644 --- a/source/detail/cell_impl.hpp +++ b/source/detail/cell_impl.hpp @@ -1,12 +1,112 @@ #pragma once +#include + #include #include +#include +#include #include #include #include "comment_impl.hpp" +namespace { + +// return s after checking encoding, size, and illegal characters +std::string check_string(std::string s) +{ + if (s.size() == 0) + { + return s; + } + + // check encoding? + + if (s.size() > 32767) + { + s = s.substr(0, 32767); // max string length in Excel + } + + for (unsigned char c : s) + { + if (c <= 8 || c == 11 || c == 12 || (c >= 14 && c <= 31)) + { + throw xlnt::illegal_character_error(static_cast(c)); + } + } + + return s; +} + +std::pair cast_numeric(const std::string &s) +{ + const char *str = s.c_str(); + char *str_end = nullptr; + auto result = std::strtold(str, &str_end); + if (str_end != str + s.size()) return{ false, 0 }; + return{ true, result }; +} + +std::pair cast_percentage(const std::string &s) +{ + if (s.back() == '%') + { + auto number = cast_numeric(s.substr(0, s.size() - 1)); + + if (number.first) + { + return{ true, number.second / 100 }; + } + } + + return { false, 0 }; +} + +std::pair cast_time(const std::string &s) +{ + xlnt::time result; + + try + { + auto last_colon = s.find_last_of(':'); + if (last_colon == std::string::npos) return { false, result }; + double seconds = std::stod(s.substr(last_colon + 1)); + result.second = static_cast(seconds); + result.microsecond = static_cast((seconds - static_cast(result.second)) * 1e6); + + auto first_colon = s.find_first_of(':'); + + if (first_colon == last_colon) + { + auto decimal_pos = s.find('.'); + if (decimal_pos != std::string::npos) + { + result.minute = std::stoi(s.substr(0, first_colon)); + } + else + { + result.hour = std::stoi(s.substr(0, first_colon)); + result.minute = result.second; + result.second = 0; + } + } + else + { + result.hour = std::stoi(s.substr(0, first_colon)); + result.minute = std::stoi(s.substr(first_colon + 1, last_colon - first_colon - 1)); + } + } + catch (std::invalid_argument) + { + return{ false, result }; + } + + return { true, result }; +} + +} // namespace + namespace xlnt { class style; @@ -22,7 +122,85 @@ struct cell_impl cell_impl(worksheet_impl *parent, column_t column, row_t row); cell_impl(const cell_impl &rhs); cell_impl &operator=(const cell_impl &rhs); + + style &get_style(bool create_if_null) + { + if(style_ == nullptr && create_if_null) + { + style_.reset(new style()); + } + + return *style_; + } + + const style &get_style() const + { + if(style_ == nullptr) + { + throw std::runtime_error("call has_style to check if const cell has a style before accessing it"); + } + + return *style_; + } + void set_date(long double number, xlnt::number_format::format format_code) + { + is_date_ = true; + auto number_format = xlnt::number_format(format_code); + get_style(true).set_number_format(number_format); + value_numeric_ = number; + type_ = cell::type::numeric; + } + + void set_string(const std::string &s, bool guess_types) + { + is_date_ = false; + value_string_ = check_string(s); + type_ = cell::type::string; + + if (value_string_.size() > 1 && value_string_.front() == '=') + { + formula_ = value_string_; + type_ = cell::type::formula; + value_string_.clear(); + } + else if(cell::ErrorCodes.find(s) != cell::ErrorCodes.end()) + { + type_ = cell::type::error; + } + else if(guess_types) + { + auto percentage = cast_percentage(s); + + if (percentage.first) + { + value_numeric_ = percentage.second; + type_ = cell::type::numeric; + get_style(true).get_number_format().set_format_code(xlnt::number_format::format::percentage); + } + else + { + auto time = cast_time(s); + + if (time.first) + { + set_date(time.second.to_number(), xlnt::number_format::format::date_time6); + } + else + { + auto numeric = cast_numeric(s); + + if (numeric.first) + { + value_numeric_ = numeric.second; + type_ = cell::type::numeric; + } + } + } + } + } + + cell::type type_; worksheet_impl *parent_; diff --git a/source/detail/include_pugixml.hpp b/source/detail/include_pugixml.hpp new file mode 100644 index 00000000..aa7e7b1e --- /dev/null +++ b/source/detail/include_pugixml.hpp @@ -0,0 +1,8 @@ +#pragma once + +/// +/// Define this here so we don't have to modify pugiconfig.hpp in pugixml source tree. +/// +#define PUGIXML_HAS_LONG_LONG + +#include diff --git a/source/style.cpp b/source/style.cpp index e0ceccb6..46642b59 100644 --- a/source/style.cpp +++ b/source/style.cpp @@ -16,22 +16,22 @@ void style::set_number_format(xlnt::number_format format) number_format_ = format; } -border style::get_border() const +border &style::get_border() { return border_; } -bool style::pivot_button() const +bool style::pivot_button() { return pivot_button_; } -bool style::quote_prefix() const +bool style::quote_prefix() { return quote_prefix_; } -alignment style::get_alignment() const +alignment &style::get_alignment() { return alignment_; } @@ -46,12 +46,12 @@ const fill &style::get_fill() const return fill_; } -font style::get_font() const +font &style::get_font() { return font_; } -protection style::get_protection() const +protection &style::get_protection() { return protection_; } diff --git a/source/workbook.cpp b/source/workbook.cpp index f347e509..02d7459a 100644 --- a/source/workbook.cpp +++ b/source/workbook.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #ifdef _WIN32 #include @@ -22,10 +21,13 @@ #include #include "detail/cell_impl.hpp" +#include "detail/include_pugixml.hpp" #include "detail/workbook_impl.hpp" #include "detail/worksheet_impl.hpp" -static std::string CreateTemporaryFilename() +namespace { + +static std::string create_temporary_filename() { #ifdef _WIN32 std::array buffer; @@ -44,6 +46,8 @@ static std::string CreateTemporaryFilename() return "/tmp/xlsx.xlnt"; #endif } + +} // namespace namespace xlnt { namespace detail { @@ -258,30 +262,39 @@ range workbook::get_named_range(const std::string &name) bool workbook::load(const std::istream &stream) { - std::string temp_file = CreateTemporaryFilename(); + std::string temp_file = create_temporary_filename(); std::ofstream tmp; + tmp.open(temp_file, std::ios::out | std::ios::binary); tmp << stream.rdbuf(); tmp.close(); + load(temp_file); + std::remove(temp_file.c_str()); + return true; } bool workbook::load(const std::vector &data) { - std::string temp_file = CreateTemporaryFilename(); + std::string temp_file = create_temporary_filename(); std::ofstream tmp; tmp.open(temp_file, std::ios::out | std::ios::binary); + for(auto c : data) { tmp.put(c); } + tmp.close(); + load(temp_file); + std::remove(temp_file.c_str()); + return true; } @@ -326,12 +339,14 @@ bool workbook::load(const std::string &filename) auto sheets_node = root_node.child("sheets"); std::vector shared_strings; + if(f.has_file("xl/sharedStrings.xml")) { shared_strings = xlnt::reader::read_shared_string(f.read("xl/sharedStrings.xml")); } std::vector number_format_ids; + if(f.has_file("xl/styles.xml")) { pugi::xml_document styles_doc; @@ -526,15 +541,19 @@ void workbook::clear() bool workbook::save(std::vector &data) { - auto temp_file = CreateTemporaryFilename(); + auto temp_file = create_temporary_filename(); save(temp_file); + std::ifstream tmp; tmp.open(temp_file, std::ios::in | std::ios::binary); + auto char_data = std::vector((std::istreambuf_iterator(tmp)), std::istreambuf_iterator()); data = std::vector(char_data.begin(), char_data.end()); tmp.close(); + std::remove(temp_file.c_str()); + return true; } diff --git a/source/worksheet.cpp b/source/worksheet.cpp index f22b8174..b77af9ae 100644 --- a/source/worksheet.cpp +++ b/source/worksheet.cpp @@ -412,7 +412,23 @@ void worksheet::unmerge_cells(column_t start_column, row_t start_row, column_t e unmerge_cells(xlnt::range_reference(start_column, start_row, end_column, end_row)); } +void worksheet::append() +{ + get_cell(cell_reference(1, get_next_row())); +} + void worksheet::append(const std::vector &cells) +{ + xlnt::cell_reference next(1, get_next_row()); + + for(auto cell : cells) + { + get_cell(next).set_value(cell); + next.set_column_index(next.get_column_index() + 1); + } +} + +row_t worksheet::get_next_row() const { int row = get_highest_row() + 1; @@ -421,51 +437,46 @@ void worksheet::append(const std::vector &cells) row = 1; } - int column = 1; - - for(auto cell : cells) - { - get_cell(cell_reference(column++, row)).set_value(cell); - } + return row; } void worksheet::append(const std::vector &cells) { - int row = get_highest_row(); - int column = 1; + xlnt::cell_reference next(1, get_next_row()); for(auto cell : cells) { - get_cell(cell_reference(column++, row)).set_value(cell); + get_cell(next).set_value(cell); + next.set_column_index(next.get_column_index() + 1); } } void worksheet::append(const std::vector &cells) { - int row = get_highest_row(); - int column = 1; + xlnt::cell_reference next(1, get_next_row()); for(auto cell : cells) { - get_cell(cell_reference(column++, row)).set_value(cell); + get_cell(next).set_value(cell); + next.set_column_index(next.get_column_index() + 1); } } void worksheet::append(const std::vector &cells) { - int row = get_highest_row(); - int column = 1; + xlnt::cell_reference next(1, get_next_row()); for(auto cell : cells) { - get_cell(cell_reference(column++, row)) = cell; + get_cell(next).set_value(cell); + next.set_column_index(next.get_column_index() + 1); } } void worksheet::append(const std::unordered_map &cells) { - int row = get_highest_row(); - + auto row = get_next_row(); + for(auto cell : cells) { get_cell(cell_reference(cell.first, row)).set_value(cell.second); @@ -474,8 +485,8 @@ void worksheet::append(const std::unordered_map &cells void worksheet::append(const std::unordered_map &cells) { - int row = get_highest_row(); - + auto row = get_next_row(); + for(auto cell : cells) { get_cell(cell_reference(cell.first, row)).set_value(cell.second); @@ -484,12 +495,12 @@ void worksheet::append(const std::unordered_map &cells) void worksheet::append(const std::vector::const_iterator begin, const std::vector::const_iterator end) { - int row = get_highest_row(); - int column = 1; + xlnt::cell_reference next(1, get_next_row()); for(auto i = begin; i != end; i++) { - get_cell(cell_reference(column++, row)).set_value(*i); + get_cell(next).set_value(*i); + next.set_column_index(next.get_column_index() + 1); } } diff --git a/tests/test_cell.hpp b/tests/test_cell.hpp index 8f8ad9d8..ad1c425d 100644 --- a/tests/test_cell.hpp +++ b/tests/test_cell.hpp @@ -374,7 +374,7 @@ public: ws.get_parent().add_font(font); auto cell = xlnt::cell(ws, "A1"); - TS_ASSERT(cell.get_font() == font); + TS_ASSERT_EQUALS(cell.get_font(), font); } void test_fill() diff --git a/tests/test_worksheet.hpp b/tests/test_worksheet.hpp index 8e18adbd..c1c96c47 100644 --- a/tests/test_worksheet.hpp +++ b/tests/test_worksheet.hpp @@ -514,7 +514,7 @@ public: void test_positioning_point() { xlnt::worksheet ws(wb_); - TS_ASSERT_EQUALS(ws.get_point_pos(40, 150), "C3"); + TS_ASSERT_EQUALS(ws.get_point_pos(150, 40), "C3"); } void test_positioning_roundtrip() @@ -552,6 +552,10 @@ public: void test_max_column() { xlnt::worksheet ws(wb_); + ws[xlnt::cell_reference("F1")].set_value(10); + ws[xlnt::cell_reference("F2")].set_value(32); + ws[xlnt::cell_reference("F3")].set_formula("=F1+F2"); + ws[xlnt::cell_reference("A4")].set_formula("=A1+A2+A3"); TS_ASSERT_EQUALS(ws.get_highest_column(), 6); } @@ -564,6 +568,10 @@ public: void test_max_row() { xlnt::worksheet ws(wb_); + ws.append(); + ws.append(std::vector { 5 }); + ws.append(); + ws.append(std::vector { 4 }); TS_ASSERT_EQUALS(ws.get_highest_row(), 4); }