From e8e29e9c18ec45fa762720d149d048f19cfcf674 Mon Sep 17 00:00:00 2001 From: JCrawfy Date: Mon, 2 Mar 2020 13:23:53 +1300 Subject: [PATCH 01/12] resolve some warnings (unintialised variables), remove warning suppression --- source/detail/serialization/xlsx_producer.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/source/detail/serialization/xlsx_producer.cpp b/source/detail/serialization/xlsx_producer.cpp index 1646b70b..3f253074 100644 --- a/source/detail/serialization/xlsx_producer.cpp +++ b/source/detail/serialization/xlsx_producer.cpp @@ -80,7 +80,9 @@ namespace detail { xlsx_producer::xlsx_producer(const workbook &target) : source_(target), - current_part_stream_(nullptr) + current_part_stream_(nullptr), + current_cell_(nullptr), + current_worksheet_(nullptr) { } @@ -918,8 +920,6 @@ void xlsx_producer::write_shared_string_table(const relationship & /*rel*/) // todo: is there a more elegant way to get this number? std::size_t string_count = 0; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wrange-loop-analysis" for (const auto ws : source_) { auto dimension = ws.calculate_dimension(); @@ -929,8 +929,8 @@ void xlsx_producer::write_shared_string_table(const relationship & /*rel*/) { while (current_cell.column() <= dimension.bottom_right().column()) { - if (ws.has_cell(current_cell) - && ws.cell(current_cell).data_type() == cell::type::shared_string) + auto c_iter = ws.d_->cell_map_.find(current_cell); + if (c_iter != ws.d_->cell_map_.end() && c_iter->second.type_ == cell_type::shared_string) { ++string_count; } @@ -942,7 +942,6 @@ void xlsx_producer::write_shared_string_table(const relationship & /*rel*/) current_cell.column_index(dimension.top_left().column_index()); } } -#pragma clang diagnostic pop write_attribute("count", string_count); write_attribute("uniqueCount", source_.shared_strings_by_id().size()); From 1069c17fbefdaecd17197b97a21d45aa44c321d5 Mon Sep 17 00:00:00 2001 From: JCrawfy Date: Mon, 2 Mar 2020 13:54:33 +1300 Subject: [PATCH 02/12] fixup comment in parser --- source/detail/serialization/xlsx_consumer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/detail/serialization/xlsx_consumer.cpp b/source/detail/serialization/xlsx_consumer.cpp index bde76349..ac90586f 100644 --- a/source/detail/serialization/xlsx_consumer.cpp +++ b/source/detail/serialization/xlsx_consumer.cpp @@ -251,7 +251,8 @@ Cell parse_cell(xlnt::row_t row_arg, xml::parser *parser) } int level = 1; // nesting level // 1 == - // 2 == // + // 2 == / + // 3 == // exit loop at while (level > 0) { @@ -272,7 +273,6 @@ Cell parse_cell(xlnt::row_t row_arg, xml::parser *parser) if (level == 2) { // -> numeric values - // -> inline string if (string_equal(parser->name(), "v")) { c.value += std::move(parser->value()); From 9136d21845729ee35b892ff1bcea3733768c4c7e Mon Sep 17 00:00:00 2001 From: JCrawfy Date: Mon, 2 Mar 2020 14:28:37 +1300 Subject: [PATCH 03/12] move the simplified cell_reference and cell structs out to a header the standard xlnt::cell and xlnt::cell_reference have plenty of extra functionality that just slows things down during (de)serialisation These intermediate structs can be used to minimise overhead before transforming to the final type --- .../serialization/serialisation_helpers.hpp | 96 +++++++++++++++++++ source/detail/serialization/xlsx_consumer.cpp | 73 ++------------ 2 files changed, 103 insertions(+), 66 deletions(-) create mode 100644 source/detail/serialization/serialisation_helpers.hpp diff --git a/source/detail/serialization/serialisation_helpers.hpp b/source/detail/serialization/serialisation_helpers.hpp new file mode 100644 index 00000000..69682b94 --- /dev/null +++ b/source/detail/serialization/serialisation_helpers.hpp @@ -0,0 +1,96 @@ +#ifndef XLNT_DETAIL_SERIALISATION_HELPERS_HPP +#define XLNT_DETAIL_SERIALISATION_HELPERS_HPP + +#include +#include +#include + +namespace xlnt { +namespace detail { + +/// parsing assumptions used by the following functions +/// - on entry, the start element for the element has been consumed by parser->next +/// - on exit, the closing element has been consumed by parser->next +/// using these assumptions, the following functions DO NOT use parser->peek (SLOW!!!) +/// probable further gains from not building an attribute map and using the attribute events instead as the impl just iterates the map + +/// 'r' == cell reference e.g. 'A1' +/// https://docs.microsoft.com/en-us/openspecs/office_standards/ms-oe376/db11a912-b1cb-4dff-b46d-9bedfd10cef0 +/// +/// a lightweight version of xlnt::cell_reference with no extre functionality (absolute/relative, ...) +/// many thousands are created during (de)serialisation, so even minor overhead is noticable +struct Cell_Reference +{ + // the obvious ctor + explicit Cell_Reference(xlnt::row_t row_arg, xlnt::column_t::index_t column_arg) noexcept + : row(row_arg), column(column_arg) + { + } + + // the common case. row # is already known during parsing (from parent element) + // just need to evaluate the column + explicit Cell_Reference(xlnt::row_t row_arg, const std::string &reference) noexcept + : row(row_arg) + { + // only three characters allowed for the column + // assumption: + // - regex pattern match: [A-Z]{1,3}\d{1,7} + const char *iter = reference.c_str(); + int temp = *iter - 'A' + 1; // 'A' == 1 + ++iter; + if (*iter >= 'A') // second char + { + temp *= 26; // LHS values are more significant + temp += *iter - 'A' + 1; // 'A' == 1 + ++iter; + if (*iter >= 'A') // third char + { + temp *= 26; // LHS values are more significant + temp += *iter - 'A' + 1; // 'A' == 1 + } + } + column = static_cast(temp); + } + + // for sorting purposes + bool operator<(const Cell_Reference &rhs) + { + // row first, serialisation is done by row then column + if (row < rhs.row) + { + return true; + } + else if (rhs.row < row) + { + return false; + } + // same row, column comparison + return column < rhs.column; + } + + xlnt::row_t row; // range:[1, 1048576] + xlnt::column_t::index_t column; // range:["A", "ZZZ"] -> [1, 26^3] -> [1, 17576] +}; + +// inside element +// https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.cell?view=openxml-2.8.1 +struct Cell +{ + // sort cells by location, row first + bool operator<(const Cell &rhs) + { + return ref < rhs.ref; + } + + bool is_phonetic = false; // 'ph' + xlnt::cell_type type = xlnt::cell_type::number; // 't' + int cell_metatdata_idx = -1; // 'cm' + int style_index = -1; // 's' + Cell_Reference ref{0, 0}; // 'r' + std::string value; // OR + std::string formula_string; // +}; + +} // namespace detail +} // namespace xlnt +#endif \ No newline at end of file diff --git a/source/detail/serialization/xlsx_consumer.cpp b/source/detail/serialization/xlsx_consumer.cpp index ac90586f..62d9c418 100644 --- a/source/detail/serialization/xlsx_consumer.cpp +++ b/source/detail/serialization/xlsx_consumer.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -127,74 +128,14 @@ void set_style_by_xfid(const std::vector &styles, } } -/// parsing assumptions used by the following functions -/// - on entry, the start element for the element has been consumed by parser->next -/// - on exit, the closing element has been consumed by parser->next -/// using these assumptions, the following functions DO NOT use parser->peek (SLOW!!!) -/// probable further gains from not building an attribute map and using the attribute events instead as the impl just iterates the map - -/// 'r' == cell reference e.g. 'A1' -/// https://docs.microsoft.com/en-us/openspecs/office_standards/ms-oe376/db11a912-b1cb-4dff-b46d-9bedfd10cef0 -/// -/// a lightweight version of xlnt::cell_reference with no extre functionality (absolute/relative, ...) -/// many thousands are created during parsing, so even minor overhead is noticable -struct Cell_Reference -{ - // not commonly used, added as the obvious ctor - explicit Cell_Reference(xlnt::row_t row_arg, xlnt::column_t::index_t column_arg) noexcept - : row(row_arg), column(column_arg) - { - } - // the common case. row # is already known during parsing (from parent element) - // just need to evaluate the column - explicit Cell_Reference(xlnt::row_t row_arg, const std::string &reference) noexcept - : row(row_arg) - { - // only three characters allowed for the column - // assumption: - // - regex pattern match: [A-Z]{1,3}\d{1,7} - const char *iter = reference.c_str(); - int temp = *iter - 'A' + 1; // 'A' == 1 - ++iter; - if (*iter >= 'A') // second char - { - temp *= 26; // LHS values are more significant - temp += *iter - 'A' + 1; // 'A' == 1 - ++iter; - if (*iter >= 'A') // third char - { - temp *= 26; // LHS values are more significant - temp += *iter - 'A' + 1; // 'A' == 1 - } - } - column = static_cast(temp); - } - - xlnt::row_t row; // range:[1, 1048576] - xlnt::column_t::index_t column; // range:["A", "ZZZ"] -> [1, 26^3] -> [1, 17576] -}; - -// inside element -// https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.cell?view=openxml-2.8.1 -struct Cell -{ - bool is_phonetic = false; // 'ph' - xlnt::cell::type type = xlnt::cell::type::number; // 't' - int cell_metatdata_idx = -1; // 'cm' - int style_index = -1; // 's' - Cell_Reference ref{0, 0}; // 'r' - std::string value; // OR - std::string formula_string; // -}; - // element struct Sheet_Data { std::vector> parsed_rows; - std::vector parsed_cells; + std::vector parsed_cells; }; -xlnt::cell::type type_from_string(const std::string &str) +xlnt::cell_type type_from_string(const std::string &str) { if (string_equal(str, "s")) { @@ -223,14 +164,14 @@ xlnt::cell::type type_from_string(const std::string &str) return xlnt::cell::type::shared_string; } -Cell parse_cell(xlnt::row_t row_arg, xml::parser *parser) +xlnt::detail::Cell parse_cell(xlnt::row_t row_arg, xml::parser *parser) { - Cell c; + xlnt::detail::Cell c; for (auto &attr : parser->attribute_map()) { if (string_equal(attr.first.name(), "r")) { - c.ref = Cell_Reference(row_arg, attr.second.value); + c.ref = xlnt::detail::Cell_Reference(row_arg, attr.second.value); } else if (string_equal(attr.first.name(), "t")) { @@ -307,7 +248,7 @@ Cell parse_cell(xlnt::row_t row_arg, xml::parser *parser) } // inside element -std::pair parse_row(xml::parser *parser, xlnt::detail::number_serialiser &converter, std::vector &parsed_cells) +std::pair parse_row(xml::parser *parser, xlnt::detail::number_serialiser &converter, std::vector &parsed_cells) { std::pair props; for (auto &attr : parser->attribute_map()) From 63484f8b8f2d645fb3aa814df67e923871024bfb Mon Sep 17 00:00:00 2001 From: JCrawfy Date: Mon, 2 Mar 2020 18:39:01 +1300 Subject: [PATCH 04/12] redo serialisation by using a sorted vector instead of a lookup for each possible row/column combination Not tested, definitely not as correct as previous implementation --- source/detail/serialization/xlsx_consumer.cpp | 4 +- source/detail/serialization/xlsx_producer.cpp | 440 +++++++++--------- 2 files changed, 227 insertions(+), 217 deletions(-) diff --git a/source/detail/serialization/xlsx_consumer.cpp b/source/detail/serialization/xlsx_consumer.cpp index 62d9c418..96c331ca 100644 --- a/source/detail/serialization/xlsx_consumer.cpp +++ b/source/detail/serialization/xlsx_consumer.cpp @@ -384,7 +384,9 @@ namespace detail { xlsx_consumer::xlsx_consumer(workbook &target) : target_(target), - parser_(nullptr) + parser_(nullptr), + current_cell_(nullptr), + current_worksheet_(nullptr) { } diff --git a/source/detail/serialization/xlsx_producer.cpp b/source/detail/serialization/xlsx_producer.cpp index 3f253074..f5580c63 100644 --- a/source/detail/serialization/xlsx_producer.cpp +++ b/source/detail/serialization/xlsx_producer.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -2281,8 +2282,59 @@ void xlsx_producer::write_worksheet(const relationship &rel) write_end_element(xmlns, "sheetPr"); } + std::vector cells; + std::vector> row_props; + std::vector> column_props; + // assume most of the cells are "live" + cells.reserve(ws.d_->cell_map_.size()); + for (const auto &cell_impl : ws.d_->cell_map_) + { + // skip cells that aren't "live" + if (cell_impl.second.is_garbage_collectible()) + { + continue; + } + cells.push_back(&cell_impl.second); + } + row_props.reserve(ws.d_->column_properties_.size()); + for (const auto &row_prop : ws.d_->row_properties_) + { + row_props.push_back(row_prop); + } + column_props.reserve(ws.d_->column_properties_.size()); + for (const auto &col_prop : ws.d_->column_properties_) + { + column_props.push_back(std::make_pair(col_prop.first.index, col_prop.second)); + } + // sorting by location makes many following operations *much* faster + std::sort(cells.begin(), cells.end(), [](const cell_impl *l, const cell_impl *r) { + // row major sort + if (l->row_ < r->row_) + { + return true; + } + if (r->row_ < l->row_) + { + return false; + } + return l->column_ < r->column_; + }); + std::sort(row_props.begin(), row_props.end(), [](const std::pair &l, const std::pair &r) { return l.first < r.first; }); + std::sort(column_props.begin(), column_props.end(), [](const std::pair &l, const std::pair &r) { return l.first < r.first; }); + write_start_element(xmlns, "dimension"); - const auto dimension = ws.calculate_dimension(); + // THIS IS WRONG. Needs to account for presence of row/column properties + // https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_dimension_topic_ID0EZ2X4.html + const auto dimension = [&]() { + if (cells.empty()) + { + return ws.calculate_dimension(); + } + else + { + return xlnt::range_reference((*cells.begin())->column_, (*cells.begin())->row_, (*cells.rbegin())->column_, (*cells.rbegin())->row_); + } + }(); write_attribute("ref", dimension.is_single_cell() ? dimension.top_left().to_string() : dimension.to_string()); write_end_element(xmlns, "dimension"); @@ -2396,57 +2448,38 @@ void xlsx_producer::write_worksheet(const relationship &rel) write_end_element(xmlns, "sheetFormatPr"); - bool has_column_properties = false; - const auto first_column = ws.lowest_column_or_props(); - const auto last_column = ws.highest_column_or_props(); - - for (auto column = first_column; column <= last_column; column++) + if (!column_props.empty()) { - if (!ws.has_column_properties(column)) continue; - - if (!has_column_properties) + write_start_element(xmlns, "cols"); + for (const auto &props : column_props) { - write_start_element(xmlns, "cols"); - has_column_properties = true; + write_start_element(xmlns, "col"); + write_attribute("min", props.first); + write_attribute("max", props.first); + + if (props.second.width.is_set()) + { + double width = (props.second.width.get() * 7 + 5) / 7; + write_attribute("width", converter_.serialise(width)); + } + if (props.second.best_fit) + { + write_attribute("bestFit", write_bool(true)); + } + if (props.second.style.is_set()) + { + write_attribute("style", props.second.style.get()); + } + if (props.second.hidden) + { + write_attribute("hidden", write_bool(true)); + } + if (props.second.custom_width) + { + write_attribute("customWidth", write_bool(true)); + } + write_end_element(xmlns, "col"); } - - const auto &props = ws.column_properties(column); - - write_start_element(xmlns, "col"); - write_attribute("min", column.index); - write_attribute("max", column.index); - - if (props.width.is_set()) - { - double width = (props.width.get() * 7 + 5) / 7; - write_attribute("width", converter_.serialise(width)); - } - - if (props.best_fit) - { - write_attribute("bestFit", write_bool(true)); - } - - if (props.style.is_set()) - { - write_attribute("style", props.style.get()); - } - - if (props.hidden) - { - write_attribute("hidden", write_bool(true)); - } - - if (props.custom_width) - { - write_attribute("customWidth", write_bool(true)); - } - - write_end_element(xmlns, "col"); - } - - if (has_column_properties) - { write_end_element(xmlns, "cols"); } @@ -2454,68 +2487,55 @@ void xlsx_producer::write_worksheet(const relationship &rel) std::vector cells_with_comments; write_start_element(xmlns, "sheetData"); - auto first_row = ws.lowest_row_or_props(); - auto last_row = ws.highest_row_or_props(); + auto first_block_column = constants::max_column(); auto last_block_column = constants::min_column(); - for (auto row = first_row; row <= last_row; ++row) + auto current_cell = cells.begin(); + auto current_row = row_props.begin(); + row_t prev_row = 0; // constants::min_row() - 1 + while (current_cell != cells.end() && current_row != row_props.end()) { - bool any_non_null = false; - auto first_check_row = row; - auto last_check_row = row; - auto first_row_in_block = row == first_row || row % 16 == 1; - + auto row = [&]() { + row_t row_tmp = constants::max_row(); + // we know atleast one of the following is valid + if (current_cell != cells.end()) + { + row_tmp = (*current_cell)->row_; + } + if (current_row != row_props.end()) + { + row_tmp = std::min(current_row->first, row_tmp); + } + return row_tmp; + }(); + // true for the first row on/after 1, 17, 33, ... (16 * x + 1) + auto first_row_in_block = prev_row == 0 || ((row - 1) / 16) > ((prev_row - 1) / 16); // 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; + first_block_column = constants::max_column().index; + last_block_column = constants::min_column().index; // round up to the next multiple of 16 - last_check_row = ((row / 16) + 1) * 16; - } - - for (auto check_row = first_check_row; check_row <= last_check_row; ++check_row) - { - for (auto column = dimension.top_left().column(); column <= dimension.bottom_right().column(); ++column) + auto last_check_row = ((row / 16) + 1) * 16; + for (auto check_cell = current_cell; check_cell != cells.end() && (*check_cell)->row_ <= last_check_row; ++check_cell) { - auto ref = cell_reference(column, check_row); - auto cell = ws.d_->cell_map_.find(ref); - if (cell == ws.d_->cell_map_.end()) - { - continue; - } - if (cell->second.is_garbage_collectible()) - { - continue; - } - - first_block_column = std::min(first_block_column, cell->second.column_); - last_block_column = std::max(last_block_column, cell->second.column_); - - if (row == check_row) - { - any_non_null = true; - } + first_block_column = std::min(first_block_column, (*check_cell)->column_); + last_block_column = std::max(last_block_column, (*check_cell)->column_); } } - if (!any_non_null && !ws.has_row_properties(row)) continue; - write_start_element(xmlns, "row"); write_attribute("r", row); auto span_string = std::to_string(first_block_column.index) + ":" + std::to_string(last_block_column.index); write_attribute("spans", span_string); - - if (ws.has_row_properties(row)) + // write properties of this row if they exist + if (current_row != row_props.end() && current_row->first == row) { - const auto &props = ws.row_properties(row); - + const auto &props = current_row->second; if (props.style.is_set()) { write_attribute("s", props.style.get()); @@ -2524,152 +2544,140 @@ void xlsx_producer::write_worksheet(const relationship &rel) { write_attribute("customFormat", write_bool(props.custom_format.get())); } - if (props.height.is_set()) { auto height = props.height.get(); write_attribute("ht", converter_.serialise(height)); } - if (props.hidden) { write_attribute("hidden", write_bool(true)); } - if (props.custom_height) { write_attribute("customHeight", write_bool(true)); } - if (props.dy_descent.is_set()) { write_attribute(xml::qname(xmlns_x14ac, "dyDescent"), props.dy_descent.get()); } + ++current_row; } - if (any_non_null) + while (current_cell != cells.end() && (*current_cell)->row_ == row) { - for (auto column = dimension.top_left().column(); column <= dimension.bottom_right().column(); ++column) + auto cell_ref = cell_reference((*current_cell)->column_, (*current_cell)->row_); + if ((*current_cell)->comment_.is_set()) { - if (!ws.has_cell(cell_reference(column, row))) continue; - - auto cell = ws.cell(cell_reference(column, row)); - - if (cell.garbage_collectible()) continue; - - // record data about the cell needed later - - if (cell.has_comment()) - { - cells_with_comments.push_back(cell.reference()); - } - - if (cell.has_hyperlink()) - { - hyperlinks.push_back(std::make_pair(cell.reference().to_string(), cell.hyperlink())); - } - - write_start_element(xmlns, "c"); - - // begin cell attributes - - 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); - } - - switch (cell.data_type()) - { - case cell::type::empty: - break; - - case cell::type::boolean: - write_attribute("t", "b"); - break; - - case cell::type::date: - write_attribute("t", "d"); - break; - - case cell::type::error: - write_attribute("t", "e"); - break; - - case cell::type::inline_string: - write_attribute("t", "inlineStr"); - break; - - case cell::type::number: // default, don't write it - //write_attribute("t", "n"); - break; - - case cell::type::shared_string: - write_attribute("t", "s"); - break; - - case cell::type::formula_string: - write_attribute("t", "str"); - break; - } - - //write_attribute("cm", ""); - //write_attribute("vm", ""); - //write_attribute("ph", ""); - - // begin child elements - - if (cell.has_formula()) - { - write_element(xmlns, "f", cell.formula()); - } - - switch (cell.data_type()) - { - case cell::type::empty: - break; - - case cell::type::boolean: - write_element(xmlns, "v", write_bool(cell.value())); - break; - - case cell::type::date: - write_element(xmlns, "v", cell.value()); - break; - - case cell::type::error: - write_element(xmlns, "v", cell.value()); - break; - - case cell::type::inline_string: - write_start_element(xmlns, "is"); - write_rich_text(xmlns, cell.value()); - write_end_element(xmlns, "is"); - break; - - case cell::type::number: - write_start_element(xmlns, "v"); - write_characters(converter_.serialise(cell.value())); - write_end_element(xmlns, "v"); - break; - - case cell::type::shared_string: - write_element(xmlns, "v", static_cast(cell.d_->value_numeric_)); - break; - - case cell::type::formula_string: - write_element(xmlns, "v", cell.value()); - break; - } - - write_end_element(xmlns, "c"); + cells_with_comments.push_back(cell_ref); } + + if ((*current_cell)->hyperlink_.is_set()) + { + // hyperlinks.push_back(std::make_pair(cell_ref.to_string(), xlnt::hyperlink(¤t_cell->hyperlink_.get()))); + } + + write_start_element(xmlns, "c"); + + // begin cell attributes + + write_attribute("r", cell_ref.to_string()); + + if ((*current_cell)->phonetics_visible_) + { + write_attribute("ph", write_bool(true)); + } + + if ((*current_cell)->format_.is_set()) + { + write_attribute("s", (*current_cell)->format_.get()->id); + } + + switch ((*current_cell)->type_) + { + case cell_type::empty: + break; + + case cell_type::boolean: + write_attribute("t", "b"); + break; + + case cell_type::date: + write_attribute("t", "d"); + break; + + case cell_type::error: + write_attribute("t", "e"); + break; + + case cell_type::inline_string: + write_attribute("t", "inlineStr"); + break; + + case cell_type::number: // default, don't write it + //write_attribute("t", "n"); + break; + + case cell_type::shared_string: + write_attribute("t", "s"); + break; + + case cell_type::formula_string: + write_attribute("t", "str"); + break; + } + + //write_attribute("cm", ""); + //write_attribute("vm", ""); + //write_attribute("ph", ""); + + // begin child elements + + if ((*current_cell)->formula_.is_set()) + { + write_element(xmlns, "f", (*current_cell)->formula_.get()); + } + + switch ((*current_cell)->type_) + { + case cell::type::empty: + break; + + case cell::type::boolean: + write_element(xmlns, "v", write_bool((*current_cell)->value_numeric_ != 0.0)); + break; + + case cell::type::date: + write_element(xmlns, "v", (*current_cell)->value_text_.plain_text()); + break; + + case cell::type::error: + write_element(xmlns, "v", (*current_cell)->value_text_.plain_text()); + break; + + case cell::type::inline_string: + write_start_element(xmlns, "is"); + write_rich_text(xmlns, (*current_cell)->value_text_); + write_end_element(xmlns, "is"); + break; + + case cell::type::number: + write_start_element(xmlns, "v"); + write_characters(converter_.serialise((*current_cell)->value_numeric_)); + write_end_element(xmlns, "v"); + break; + + case cell::type::shared_string: + write_element(xmlns, "v", static_cast((*current_cell)->value_numeric_)); + break; + + case cell::type::formula_string: + write_element(xmlns, "v", (*current_cell)->value_text_.plain_text()); + break; + } + + write_end_element(xmlns, "c"); + ++current_cell; } write_end_element(xmlns, "row"); @@ -3069,7 +3077,7 @@ void xlsx_producer::write_worksheet(const relationship &rel) } } } -} +} // namespace detail // Sheet Relationship Target Parts From dfb8f1518e82d253c479f6521670e9e6934ca399 Mon Sep 17 00:00:00 2001 From: JCrawfy Date: Sat, 25 Apr 2020 11:15:23 +1200 Subject: [PATCH 05/12] Revert "redo serialisation by using a sorted vector instead of a lookup for each possible row/column combination" This reverts commit 63484f8b8f2d645fb3aa814df67e923871024bfb. --- source/detail/serialization/xlsx_consumer.cpp | 4 +- source/detail/serialization/xlsx_producer.cpp | 442 +++++++++--------- 2 files changed, 218 insertions(+), 228 deletions(-) diff --git a/source/detail/serialization/xlsx_consumer.cpp b/source/detail/serialization/xlsx_consumer.cpp index 96c331ca..62d9c418 100644 --- a/source/detail/serialization/xlsx_consumer.cpp +++ b/source/detail/serialization/xlsx_consumer.cpp @@ -384,9 +384,7 @@ namespace detail { xlsx_consumer::xlsx_consumer(workbook &target) : target_(target), - parser_(nullptr), - current_cell_(nullptr), - current_worksheet_(nullptr) + parser_(nullptr) { } diff --git a/source/detail/serialization/xlsx_producer.cpp b/source/detail/serialization/xlsx_producer.cpp index f5580c63..3f253074 100644 --- a/source/detail/serialization/xlsx_producer.cpp +++ b/source/detail/serialization/xlsx_producer.cpp @@ -40,7 +40,6 @@ #include #include #include -#include #include #include #include @@ -2282,59 +2281,8 @@ void xlsx_producer::write_worksheet(const relationship &rel) write_end_element(xmlns, "sheetPr"); } - std::vector cells; - std::vector> row_props; - std::vector> column_props; - // assume most of the cells are "live" - cells.reserve(ws.d_->cell_map_.size()); - for (const auto &cell_impl : ws.d_->cell_map_) - { - // skip cells that aren't "live" - if (cell_impl.second.is_garbage_collectible()) - { - continue; - } - cells.push_back(&cell_impl.second); - } - row_props.reserve(ws.d_->column_properties_.size()); - for (const auto &row_prop : ws.d_->row_properties_) - { - row_props.push_back(row_prop); - } - column_props.reserve(ws.d_->column_properties_.size()); - for (const auto &col_prop : ws.d_->column_properties_) - { - column_props.push_back(std::make_pair(col_prop.first.index, col_prop.second)); - } - // sorting by location makes many following operations *much* faster - std::sort(cells.begin(), cells.end(), [](const cell_impl *l, const cell_impl *r) { - // row major sort - if (l->row_ < r->row_) - { - return true; - } - if (r->row_ < l->row_) - { - return false; - } - return l->column_ < r->column_; - }); - std::sort(row_props.begin(), row_props.end(), [](const std::pair &l, const std::pair &r) { return l.first < r.first; }); - std::sort(column_props.begin(), column_props.end(), [](const std::pair &l, const std::pair &r) { return l.first < r.first; }); - write_start_element(xmlns, "dimension"); - // THIS IS WRONG. Needs to account for presence of row/column properties - // https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_dimension_topic_ID0EZ2X4.html - const auto dimension = [&]() { - if (cells.empty()) - { - return ws.calculate_dimension(); - } - else - { - return xlnt::range_reference((*cells.begin())->column_, (*cells.begin())->row_, (*cells.rbegin())->column_, (*cells.rbegin())->row_); - } - }(); + const auto dimension = ws.calculate_dimension(); write_attribute("ref", dimension.is_single_cell() ? dimension.top_left().to_string() : dimension.to_string()); write_end_element(xmlns, "dimension"); @@ -2448,38 +2396,57 @@ void xlsx_producer::write_worksheet(const relationship &rel) write_end_element(xmlns, "sheetFormatPr"); - if (!column_props.empty()) - { - write_start_element(xmlns, "cols"); - for (const auto &props : column_props) - { - write_start_element(xmlns, "col"); - write_attribute("min", props.first); - write_attribute("max", props.first); + bool has_column_properties = false; + const auto first_column = ws.lowest_column_or_props(); + const auto last_column = ws.highest_column_or_props(); - if (props.second.width.is_set()) - { - double width = (props.second.width.get() * 7 + 5) / 7; - write_attribute("width", converter_.serialise(width)); - } - if (props.second.best_fit) - { - write_attribute("bestFit", write_bool(true)); - } - if (props.second.style.is_set()) - { - write_attribute("style", props.second.style.get()); - } - if (props.second.hidden) - { - write_attribute("hidden", write_bool(true)); - } - if (props.second.custom_width) - { - write_attribute("customWidth", write_bool(true)); - } - write_end_element(xmlns, "col"); + for (auto column = first_column; column <= last_column; column++) + { + if (!ws.has_column_properties(column)) continue; + + if (!has_column_properties) + { + write_start_element(xmlns, "cols"); + has_column_properties = true; } + + const auto &props = ws.column_properties(column); + + write_start_element(xmlns, "col"); + write_attribute("min", column.index); + write_attribute("max", column.index); + + if (props.width.is_set()) + { + double width = (props.width.get() * 7 + 5) / 7; + write_attribute("width", converter_.serialise(width)); + } + + if (props.best_fit) + { + write_attribute("bestFit", write_bool(true)); + } + + if (props.style.is_set()) + { + write_attribute("style", props.style.get()); + } + + if (props.hidden) + { + write_attribute("hidden", write_bool(true)); + } + + if (props.custom_width) + { + write_attribute("customWidth", write_bool(true)); + } + + write_end_element(xmlns, "col"); + } + + if (has_column_properties) + { write_end_element(xmlns, "cols"); } @@ -2487,55 +2454,68 @@ void xlsx_producer::write_worksheet(const relationship &rel) std::vector cells_with_comments; write_start_element(xmlns, "sheetData"); - + auto first_row = ws.lowest_row_or_props(); + auto last_row = ws.highest_row_or_props(); auto first_block_column = constants::max_column(); auto last_block_column = constants::min_column(); - auto current_cell = cells.begin(); - auto current_row = row_props.begin(); - row_t prev_row = 0; // constants::min_row() - 1 - while (current_cell != cells.end() && current_row != row_props.end()) + for (auto row = first_row; row <= last_row; ++row) { - auto row = [&]() { - row_t row_tmp = constants::max_row(); - // we know atleast one of the following is valid - if (current_cell != cells.end()) - { - row_tmp = (*current_cell)->row_; - } - if (current_row != row_props.end()) - { - row_tmp = std::min(current_row->first, row_tmp); - } - return row_tmp; - }(); - // true for the first row on/after 1, 17, 33, ... (16 * x + 1) - auto first_row_in_block = prev_row == 0 || ((row - 1) / 16) > ((prev_row - 1) / 16); + bool any_non_null = false; + auto first_check_row = row; + auto last_check_row = row; + auto first_row_in_block = row == first_row || row % 16 == 1; + // 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().index; - last_block_column = constants::min_column().index; + first_block_column = constants::max_column(); + last_block_column = constants::min_column(); + + first_check_row = row; // round up to the next multiple of 16 - auto last_check_row = ((row / 16) + 1) * 16; - for (auto check_cell = current_cell; check_cell != cells.end() && (*check_cell)->row_ <= last_check_row; ++check_cell) + last_check_row = ((row / 16) + 1) * 16; + } + + for (auto check_row = first_check_row; check_row <= last_check_row; ++check_row) + { + for (auto column = dimension.top_left().column(); column <= dimension.bottom_right().column(); ++column) { - first_block_column = std::min(first_block_column, (*check_cell)->column_); - last_block_column = std::max(last_block_column, (*check_cell)->column_); + auto ref = cell_reference(column, check_row); + auto cell = ws.d_->cell_map_.find(ref); + if (cell == ws.d_->cell_map_.end()) + { + continue; + } + if (cell->second.is_garbage_collectible()) + { + continue; + } + + first_block_column = std::min(first_block_column, cell->second.column_); + last_block_column = std::max(last_block_column, cell->second.column_); + + if (row == check_row) + { + any_non_null = true; + } } } + if (!any_non_null && !ws.has_row_properties(row)) continue; + write_start_element(xmlns, "row"); write_attribute("r", row); auto span_string = std::to_string(first_block_column.index) + ":" + std::to_string(last_block_column.index); write_attribute("spans", span_string); - // write properties of this row if they exist - if (current_row != row_props.end() && current_row->first == row) + + if (ws.has_row_properties(row)) { - const auto &props = current_row->second; + const auto &props = ws.row_properties(row); + if (props.style.is_set()) { write_attribute("s", props.style.get()); @@ -2544,140 +2524,152 @@ void xlsx_producer::write_worksheet(const relationship &rel) { write_attribute("customFormat", write_bool(props.custom_format.get())); } + if (props.height.is_set()) { auto height = props.height.get(); write_attribute("ht", converter_.serialise(height)); } + if (props.hidden) { write_attribute("hidden", write_bool(true)); } + if (props.custom_height) { write_attribute("customHeight", write_bool(true)); } + if (props.dy_descent.is_set()) { write_attribute(xml::qname(xmlns_x14ac, "dyDescent"), props.dy_descent.get()); } - ++current_row; } - while (current_cell != cells.end() && (*current_cell)->row_ == row) + if (any_non_null) { - auto cell_ref = cell_reference((*current_cell)->column_, (*current_cell)->row_); - if ((*current_cell)->comment_.is_set()) + for (auto column = dimension.top_left().column(); column <= dimension.bottom_right().column(); ++column) { - cells_with_comments.push_back(cell_ref); + if (!ws.has_cell(cell_reference(column, row))) continue; + + auto cell = ws.cell(cell_reference(column, row)); + + if (cell.garbage_collectible()) continue; + + // record data about the cell needed later + + if (cell.has_comment()) + { + cells_with_comments.push_back(cell.reference()); + } + + if (cell.has_hyperlink()) + { + hyperlinks.push_back(std::make_pair(cell.reference().to_string(), cell.hyperlink())); + } + + write_start_element(xmlns, "c"); + + // begin cell attributes + + 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); + } + + switch (cell.data_type()) + { + case cell::type::empty: + break; + + case cell::type::boolean: + write_attribute("t", "b"); + break; + + case cell::type::date: + write_attribute("t", "d"); + break; + + case cell::type::error: + write_attribute("t", "e"); + break; + + case cell::type::inline_string: + write_attribute("t", "inlineStr"); + break; + + case cell::type::number: // default, don't write it + //write_attribute("t", "n"); + break; + + case cell::type::shared_string: + write_attribute("t", "s"); + break; + + case cell::type::formula_string: + write_attribute("t", "str"); + break; + } + + //write_attribute("cm", ""); + //write_attribute("vm", ""); + //write_attribute("ph", ""); + + // begin child elements + + if (cell.has_formula()) + { + write_element(xmlns, "f", cell.formula()); + } + + switch (cell.data_type()) + { + case cell::type::empty: + break; + + case cell::type::boolean: + write_element(xmlns, "v", write_bool(cell.value())); + break; + + case cell::type::date: + write_element(xmlns, "v", cell.value()); + break; + + case cell::type::error: + write_element(xmlns, "v", cell.value()); + break; + + case cell::type::inline_string: + write_start_element(xmlns, "is"); + write_rich_text(xmlns, cell.value()); + write_end_element(xmlns, "is"); + break; + + case cell::type::number: + write_start_element(xmlns, "v"); + write_characters(converter_.serialise(cell.value())); + write_end_element(xmlns, "v"); + break; + + case cell::type::shared_string: + write_element(xmlns, "v", static_cast(cell.d_->value_numeric_)); + break; + + case cell::type::formula_string: + write_element(xmlns, "v", cell.value()); + break; + } + + write_end_element(xmlns, "c"); } - - if ((*current_cell)->hyperlink_.is_set()) - { - // hyperlinks.push_back(std::make_pair(cell_ref.to_string(), xlnt::hyperlink(¤t_cell->hyperlink_.get()))); - } - - write_start_element(xmlns, "c"); - - // begin cell attributes - - write_attribute("r", cell_ref.to_string()); - - if ((*current_cell)->phonetics_visible_) - { - write_attribute("ph", write_bool(true)); - } - - if ((*current_cell)->format_.is_set()) - { - write_attribute("s", (*current_cell)->format_.get()->id); - } - - switch ((*current_cell)->type_) - { - case cell_type::empty: - break; - - case cell_type::boolean: - write_attribute("t", "b"); - break; - - case cell_type::date: - write_attribute("t", "d"); - break; - - case cell_type::error: - write_attribute("t", "e"); - break; - - case cell_type::inline_string: - write_attribute("t", "inlineStr"); - break; - - case cell_type::number: // default, don't write it - //write_attribute("t", "n"); - break; - - case cell_type::shared_string: - write_attribute("t", "s"); - break; - - case cell_type::formula_string: - write_attribute("t", "str"); - break; - } - - //write_attribute("cm", ""); - //write_attribute("vm", ""); - //write_attribute("ph", ""); - - // begin child elements - - if ((*current_cell)->formula_.is_set()) - { - write_element(xmlns, "f", (*current_cell)->formula_.get()); - } - - switch ((*current_cell)->type_) - { - case cell::type::empty: - break; - - case cell::type::boolean: - write_element(xmlns, "v", write_bool((*current_cell)->value_numeric_ != 0.0)); - break; - - case cell::type::date: - write_element(xmlns, "v", (*current_cell)->value_text_.plain_text()); - break; - - case cell::type::error: - write_element(xmlns, "v", (*current_cell)->value_text_.plain_text()); - break; - - case cell::type::inline_string: - write_start_element(xmlns, "is"); - write_rich_text(xmlns, (*current_cell)->value_text_); - write_end_element(xmlns, "is"); - break; - - case cell::type::number: - write_start_element(xmlns, "v"); - write_characters(converter_.serialise((*current_cell)->value_numeric_)); - write_end_element(xmlns, "v"); - break; - - case cell::type::shared_string: - write_element(xmlns, "v", static_cast((*current_cell)->value_numeric_)); - break; - - case cell::type::formula_string: - write_element(xmlns, "v", (*current_cell)->value_text_.plain_text()); - break; - } - - write_end_element(xmlns, "c"); - ++current_cell; } write_end_element(xmlns, "row"); @@ -3077,7 +3069,7 @@ void xlsx_producer::write_worksheet(const relationship &rel) } } } -} // namespace detail +} // Sheet Relationship Target Parts From d30e705f836a98cb9e20d02fe445083e9f691198 Mon Sep 17 00:00:00 2001 From: JCrawfy Date: Sat, 25 Apr 2020 14:00:58 +1200 Subject: [PATCH 06/12] fix most (all?) the places where string<->double conversions are performed strod / stod / to_string and all related friends are dependant on current locale for how they format a number --- include/xlnt/utils/numeric.hpp | 34 ++++++++++++----- source/cell/cell.cpp | 15 ++++---- .../detail/number_format/number_formatter.cpp | 37 ++++++------------- .../detail/number_format/number_formatter.hpp | 2 + source/detail/serialization/xlsx_producer.cpp | 33 +++-------------- source/detail/serialization/xlsx_producer.hpp | 23 ++++++++++-- 6 files changed, 70 insertions(+), 74 deletions(-) diff --git a/include/xlnt/utils/numeric.hpp b/include/xlnt/utils/numeric.hpp index 6022484e..c2ade2e6 100644 --- a/include/xlnt/utils/numeric.hpp +++ b/include/xlnt/utils/numeric.hpp @@ -129,10 +129,12 @@ class number_serialiser public: explicit number_serialiser() - : should_convert_comma(std::use_facet>(std::locale{}).decimal_point() == ',') + : should_convert_comma(localeconv()->decimal_point[0] == ',') { } + // for printing to file. + // This matches the output format of excel irrespective of current locale std::string serialise(double d) const { char buf[30]; @@ -144,29 +146,43 @@ public: return std::string(buf, static_cast(len)); } - double deserialise(std::string &s) const noexcept + // replacement for std::to_string / s*printf("%f", ...) + // behaves same irrespective of locale + std::string serialise_short(double d) const { - assert(!s.empty()); + char buf[30]; + int len = snprintf(buf, sizeof(buf), "%f", d); if (should_convert_comma) { - // s.data() doesn't have a non-const overload until c++17, hence this little dance - convert_pt_to_comma(&s[0], s.size()); + convert_comma_to_pt(buf, len); } - return strtod(s.c_str(), nullptr); + return std::string(buf, static_cast(len)); } - double deserialise(const std::string &s) const + double deserialise(const std::string &s, ptrdiff_t *len_converted) const { assert(!s.empty()); + assert(len_converted != nullptr); + char *end_of_convert; if (!should_convert_comma) { - return strtod(s.c_str(), nullptr); + double d = strtod(s.c_str(), &end_of_convert); + *len_converted = end_of_convert - s.c_str(); + return d; } char buf[30]; assert(s.size() < sizeof(buf)); auto copy_end = std::copy(s.begin(), s.end(), buf); convert_pt_to_comma(buf, static_cast(copy_end - buf)); - return strtod(buf, nullptr); + double d = strtod(buf, &end_of_convert); + *len_converted = end_of_convert - buf; + return d; + } + + double deserialise(const std::string &s) const + { + ptrdiff_t ignore; + return deserialise(s, &ignore); } }; diff --git a/source/cell/cell.cpp b/source/cell/cell.cpp index e47f2a93..ed496c8b 100644 --- a/source/cell/cell.cpp +++ b/source/cell/cell.cpp @@ -57,15 +57,16 @@ #include #include #include +#include namespace { std::pair cast_numeric(const std::string &s) { - auto str_end = static_cast(nullptr); - auto result = std::strtod(s.c_str(), &str_end); - - return (str_end != s.c_str() + s.size()) + xlnt::detail::number_serialiser ser; + ptrdiff_t len_convert; + double result = ser.deserialise(s, &len_convert); + return (len_convert != static_cast(s.size())) ? std::make_pair(false, 0.0) : std::make_pair(true, result); } @@ -108,7 +109,7 @@ std::pair cast_time(const std::string &s) } std::vector numeric_components; - + xlnt::detail::number_serialiser ser; for (auto component : time_components) { if (component.empty() || (component.substr(0, component.find('.')).size() > 2)) @@ -123,9 +124,7 @@ std::pair cast_time(const std::string &s) return {false, result}; } } - - auto without_leading_zero = component.front() == '0' ? component.substr(1) : component; - auto numeric = std::stod(without_leading_zero); + auto numeric = ser.deserialise(component); numeric_components.push_back(numeric); } diff --git a/source/detail/number_format/number_formatter.cpp b/source/detail/number_format/number_formatter.cpp index b116fd0e..bd5fd3f9 100644 --- a/source/detail/number_format/number_formatter.cpp +++ b/source/detail/number_format/number_formatter.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -622,7 +623,8 @@ void number_format_parser::parse() value = token.string.substr(1); } - section.condition.value = std::stod(value); + detail::number_serialiser ser; + section.condition.value = ser.deserialise(value); break; } @@ -1565,19 +1567,7 @@ std::string number_formatter::fill_placeholders(const format_placeholders &p, do if (p.type == format_placeholders::placeholders_type::general || p.type == format_placeholders::placeholders_type::text) { - result = std::to_string(number); - - while (result.back() == '0') - { - result.pop_back(); - } - - if (result.back() == '.') - { - result.pop_back(); - } - - return result; + return serialiser_.serialise_short(number); } if (p.percentage) @@ -1636,21 +1626,22 @@ std::string number_formatter::fill_placeholders(const format_placeholders &p, do auto fractional_part = number - integer_part; result = std::fabs(fractional_part) < std::numeric_limits::min() ? std::string(".") - : std::to_string(fractional_part).substr(1); + : serialiser_.serialise_short(fractional_part).substr(1); while (result.back() == '0' || result.size() > (p.num_zeros + p.num_optionals + p.num_spaces + 1)) { result.pop_back(); } - while (result.size() < p.num_zeros + 1) + + if (result.size() < p.num_zeros + 1) { - result.push_back('0'); + result.resize(p.num_zeros + 1, '0'); } - while (result.size() < p.num_zeros + p.num_optionals + p.num_spaces + 1) + if (result.size() < p.num_zeros + p.num_optionals + p.num_spaces + 1) { - result.push_back(' '); + result.resize(p.num_zeros + p.num_optionals + p.num_spaces + 1, ' '); } if (p.percentage) @@ -1689,13 +1680,7 @@ std::string number_formatter::fill_scientific_placeholders(const format_placehol integer_string = std::string(integer_part.num_zeros + integer_part.num_optionals, '0'); } - std::string fractional_string = std::to_string(fraction).substr(1); - - while (fractional_string.size() > fractional_part.num_zeros + fractional_part.num_optionals + 1) - { - fractional_string.pop_back(); - } - + std::string fractional_string = serialiser_.serialise_short(fraction).substr(1, fractional_part.num_zeros + fractional_part.num_optionals + 1); std::string exponent_string = std::to_string(logarithm); while (exponent_string.size() < fractional_part.num_zeros) diff --git a/source/detail/number_format/number_formatter.hpp b/source/detail/number_format/number_formatter.hpp index 6b7a11ea..0f08992e 100644 --- a/source/detail/number_format/number_formatter.hpp +++ b/source/detail/number_format/number_formatter.hpp @@ -28,6 +28,7 @@ #include #include +#include namespace xlnt { namespace detail { @@ -691,6 +692,7 @@ private: number_format_parser parser_; std::vector format_; xlnt::calendar calendar_; + xlnt::detail::number_serialiser serialiser_; }; } // namespace detail diff --git a/source/detail/serialization/xlsx_producer.cpp b/source/detail/serialization/xlsx_producer.cpp index 3f253074..e6683a31 100644 --- a/source/detail/serialization/xlsx_producer.cpp +++ b/source/detail/serialization/xlsx_producer.cpp @@ -2813,33 +2813,12 @@ void xlsx_producer::write_worksheet(const relationship &rel) { write_start_element(xmlns, "pageMargins"); - // TODO: there must be a better way to do this - auto remove_trailing_zeros = [](const std::string &n) -> std::string { - auto decimal = n.find('.'); - - if (decimal == std::string::npos) return n; - - auto index = n.size() - 1; - - while (index >= decimal && n[index] == '0') - { - index--; - } - - if (index == decimal) - { - return n.substr(0, decimal); - } - - return n.substr(0, index + 1); - }; - - write_attribute("left", remove_trailing_zeros(std::to_string(ws.page_margins().left()))); - write_attribute("right", remove_trailing_zeros(std::to_string(ws.page_margins().right()))); - write_attribute("top", remove_trailing_zeros(std::to_string(ws.page_margins().top()))); - write_attribute("bottom", remove_trailing_zeros(std::to_string(ws.page_margins().bottom()))); - write_attribute("header", remove_trailing_zeros(std::to_string(ws.page_margins().header()))); - write_attribute("footer", remove_trailing_zeros(std::to_string(ws.page_margins().footer()))); + write_attribute("left", ws.page_margins().left()); + write_attribute("right", ws.page_margins().right()); + write_attribute("top", ws.page_margins().top()); + write_attribute("bottom", ws.page_margins().bottom()); + write_attribute("header", ws.page_margins().header()); + write_attribute("footer", ws.page_margins().footer()); write_end_element(xmlns, "pageMargins"); } diff --git a/source/detail/serialization/xlsx_producer.hpp b/source/detail/serialization/xlsx_producer.hpp index 4660f3c2..ecd102f8 100644 --- a/source/detail/serialization/xlsx_producer.hpp +++ b/source/detail/serialization/xlsx_producer.hpp @@ -28,9 +28,9 @@ #include #include +#include #include #include -#include namespace xml { class serializer; @@ -169,19 +169,34 @@ private: void write_namespace(const std::string &ns, const std::string &prefix); - template + // std::string attribute name + // not integer or float type + template >> void write_attribute(const std::string &name, T value) { current_part_serializer_->attribute(name, value); } - template + void write_attribute(const std::string &name, double value) + { + current_part_serializer_->attribute(name, converter_.serialise(value)); + } + + // qname attribute name + // not integer or float type + template >> void write_attribute(const xml::qname &name, T value) { current_part_serializer_->attribute(name, value); } - template + void write_attribute(const xml::qname &name, double value) + { + current_part_serializer_->attribute(name, converter_.serialise(value)); + } + + + template void write_characters(T characters, bool preserve_whitespace = false) { if (preserve_whitespace) From 556d3358e90991f477b5020aa71748ceb3148dab Mon Sep 17 00:00:00 2001 From: JCrawfy Date: Sun, 26 Apr 2020 14:53:15 +1200 Subject: [PATCH 07/12] fix bad commit --- source/detail/number_format/number_formatter.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/source/detail/number_format/number_formatter.cpp b/source/detail/number_format/number_formatter.cpp index bd5fd3f9..7ffcd7c5 100644 --- a/source/detail/number_format/number_formatter.cpp +++ b/source/detail/number_format/number_formatter.cpp @@ -1567,7 +1567,16 @@ std::string number_formatter::fill_placeholders(const format_placeholders &p, do if (p.type == format_placeholders::placeholders_type::general || p.type == format_placeholders::placeholders_type::text) { - return serialiser_.serialise_short(number); + auto s = serialiser_.serialise_short(number); + while (s.size() > 1 && s.back() == '0') + { + s.pop_back(); + } + if (s.back() == '.') + { + s.pop_back(); + } + return s; } if (p.percentage) From 9d9d5de5113eaa42ba11cc695eeb5a0465dd87f8 Mon Sep 17 00:00:00 2001 From: JCrawfy Date: Sun, 26 Apr 2020 15:27:51 +1200 Subject: [PATCH 08/12] add missing include --- include/xlnt/utils/numeric.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/xlnt/utils/numeric.hpp b/include/xlnt/utils/numeric.hpp index c2ade2e6..1f6c182a 100644 --- a/include/xlnt/utils/numeric.hpp +++ b/include/xlnt/utils/numeric.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include From 504fed3585fe1935f2ab33a31a51832a5622035d Mon Sep 17 00:00:00 2001 From: JCrawfy Date: Sun, 26 Apr 2020 15:40:56 +1200 Subject: [PATCH 09/12] another missing header --- source/detail/serialization/xlsx_producer.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/source/detail/serialization/xlsx_producer.hpp b/source/detail/serialization/xlsx_producer.hpp index ecd102f8..5dd1534a 100644 --- a/source/detail/serialization/xlsx_producer.hpp +++ b/source/detail/serialization/xlsx_producer.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include From f1042c5119f35b73d91606aeca4b4a78e4a7ad76 Mon Sep 17 00:00:00 2001 From: JCrawfy Date: Sun, 26 Apr 2020 15:47:53 +1200 Subject: [PATCH 10/12] enable_if_t isn't a thing in C++11 --- source/detail/serialization/xlsx_producer.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/detail/serialization/xlsx_producer.hpp b/source/detail/serialization/xlsx_producer.hpp index 5dd1534a..aa324a92 100644 --- a/source/detail/serialization/xlsx_producer.hpp +++ b/source/detail/serialization/xlsx_producer.hpp @@ -172,7 +172,7 @@ private: // std::string attribute name // not integer or float type - template >> + template >::type> void write_attribute(const std::string &name, T value) { current_part_serializer_->attribute(name, value); @@ -185,7 +185,7 @@ private: // qname attribute name // not integer or float type - template >> + template >::type> void write_attribute(const xml::qname &name, T value) { current_part_serializer_->attribute(name, value); From 6c5a5a5daefed2cd4018cd57867c86ceb221291c Mon Sep 17 00:00:00 2001 From: JCrawfy Date: Sun, 26 Apr 2020 15:49:05 +1200 Subject: [PATCH 11/12] and same issue with is_convertible --- source/detail/serialization/xlsx_producer.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/detail/serialization/xlsx_producer.hpp b/source/detail/serialization/xlsx_producer.hpp index aa324a92..7100274f 100644 --- a/source/detail/serialization/xlsx_producer.hpp +++ b/source/detail/serialization/xlsx_producer.hpp @@ -172,7 +172,7 @@ private: // std::string attribute name // not integer or float type - template >::type> + template ::value>::type> void write_attribute(const std::string &name, T value) { current_part_serializer_->attribute(name, value); @@ -185,7 +185,7 @@ private: // qname attribute name // not integer or float type - template >::type> + template ::value>::type> void write_attribute(const xml::qname &name, T value) { current_part_serializer_->attribute(name, value); From 06801f7d362ffec4933f56dd83c007a3c549a9e2 Mon Sep 17 00:00:00 2001 From: JCrawfy Date: Sun, 26 Apr 2020 15:54:49 +1200 Subject: [PATCH 12/12] derp, need typename too --- source/detail/serialization/xlsx_producer.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/detail/serialization/xlsx_producer.hpp b/source/detail/serialization/xlsx_producer.hpp index 7100274f..80afdf01 100644 --- a/source/detail/serialization/xlsx_producer.hpp +++ b/source/detail/serialization/xlsx_producer.hpp @@ -172,7 +172,7 @@ private: // std::string attribute name // not integer or float type - template ::value>::type> + template ::value>::type> void write_attribute(const std::string &name, T value) { current_part_serializer_->attribute(name, value); @@ -185,7 +185,7 @@ private: // qname attribute name // not integer or float type - template ::value>::type> + template ::value>::type> void write_attribute(const xml::qname &name, T value) { current_part_serializer_->attribute(name, value);