diff --git a/benchmarks/spreadsheet-load.cpp b/benchmarks/spreadsheet-load.cpp new file mode 100644 index 00000000..0caadb02 --- /dev/null +++ b/benchmarks/spreadsheet-load.cpp @@ -0,0 +1,33 @@ +#include +#include +#include + +namespace { +using milliseconds_d = std::chrono::duration; + +void run_test(const xlnt::path &file, int runs = 10) +{ + std::cout << file.string() << "\n\n"; + + xlnt::workbook wb; + std::vector test_timings; + + for (int i = 0; i < runs; ++i) + { + auto start = std::chrono::steady_clock::now(); + wb.load(file); + + auto end = std::chrono::steady_clock::now(); + wb.clear(); + test_timings.push_back(end - start); + + std::cout << milliseconds_d(test_timings.back()).count() << " ms\n"; + } +} +} // namespace + +int main() +{ + run_test(path_helper::benchmark_file("large.xlsx")); + run_test(path_helper::benchmark_file("very_large.xlsx")); +} \ No newline at end of file diff --git a/include/xlnt/styles/format.hpp b/include/xlnt/styles/format.hpp index 252f7cb8..e006ae89 100755 --- a/include/xlnt/styles/format.hpp +++ b/include/xlnt/styles/format.hpp @@ -214,6 +214,7 @@ public: private: friend struct detail::stylesheet; friend class detail::xlsx_producer; + friend class detail::xlsx_consumer; friend class cell; /// diff --git a/source/detail/numeric_utils.hpp b/include/xlnt/utils/numeric.hpp similarity index 80% rename from source/detail/numeric_utils.hpp rename to include/xlnt/utils/numeric.hpp index 97723772..285c4da2 100644 --- a/source/detail/numeric_utils.hpp +++ b/include/xlnt/utils/numeric.hpp @@ -28,9 +28,12 @@ #include #include #include +#include +#include namespace xlnt { namespace detail { + /// /// Takes in any number and outputs a string form of that number which will /// serialise and deserialise without loss of precision @@ -84,8 +87,7 @@ constexpr typename std::common_type::type min(NumberL lval, Nu /// template // parameter types (deduced) -bool -float_equals(const LNumber &lhs, const RNumber &rhs, +bool float_equals(const LNumber &lhs, const RNumber &rhs, int epsilon_scale = 20) // scale the "fuzzy" equality. Higher value gives a more tolerant comparison { // a type that lhs and rhs can agree on @@ -111,9 +113,50 @@ float_equals(const LNumber &lhs, const RNumber &rhs, // additionally, a scale factor is applied. common_t scaled_fuzz = epsilon_scale * epsilon * max(max(xlnt::detail::abs(lhs), xlnt::detail::abs(rhs)), // |max| of parameters. - common_t{1}); // clamp + common_t{1}); // clamp return ((lhs + scaled_fuzz) >= rhs) && ((rhs + scaled_fuzz) >= lhs); } +struct number_converter +{ + explicit number_converter() + : should_convert_to_comma(std::use_facet>(std::locale{}).decimal_point() == ',') + { + } + + double stold(std::string &s) const noexcept + { + assert(!s.empty()); + if (should_convert_to_comma) + { + auto decimal_pt = std::find(s.begin(), s.end(), '.'); + if (decimal_pt != s.end()) + { + *decimal_pt = ','; + } + } + return strtod(s.c_str(), nullptr); + } + + double stold(const std::string &s) const + { + assert(!s.empty()); + if (!should_convert_to_comma) + { + return strtod(s.c_str(), nullptr); + } + std::string copy(s); + auto decimal_pt = std::find(copy.begin(), copy.end(), '.'); + if (decimal_pt != copy.end()) + { + *decimal_pt = ','; + } + return strtod(copy.c_str(), nullptr); + } + +private: + bool should_convert_to_comma = false; +}; + } // namespace detail } // namespace xlnt diff --git a/include/xlnt/utils/optional.hpp b/include/xlnt/utils/optional.hpp index 4f5b6b78..1337f040 100644 --- a/include/xlnt/utils/optional.hpp +++ b/include/xlnt/utils/optional.hpp @@ -25,7 +25,7 @@ #include "xlnt/xlnt_config.hpp" #include "xlnt/utils/exceptions.hpp" -#include "../source/detail/numeric_utils.hpp" +#include "xlnt/utils/numeric.hpp" #include namespace xlnt { diff --git a/include/xlnt/worksheet/sheet_format_properties.hpp b/include/xlnt/worksheet/sheet_format_properties.hpp index dd63685d..b6223954 100644 --- a/include/xlnt/worksheet/sheet_format_properties.hpp +++ b/include/xlnt/worksheet/sheet_format_properties.hpp @@ -25,7 +25,7 @@ #include #include -#include "../source/detail/numeric_utils.hpp" +#include namespace xlnt { diff --git a/source/detail/header_footer/header_footer_code.cpp b/source/detail/header_footer/header_footer_code.cpp index b678ef90..7e4da211 100644 --- a/source/detail/header_footer/header_footer_code.cpp +++ b/source/detail/header_footer/header_footer_code.cpp @@ -22,7 +22,7 @@ // @author: see AUTHORS file #include -#include +//#include namespace xlnt { namespace detail { diff --git a/source/detail/implementations/cell_impl.hpp b/source/detail/implementations/cell_impl.hpp index 1ead8557..b72e3c47 100644 --- a/source/detail/implementations/cell_impl.hpp +++ b/source/detail/implementations/cell_impl.hpp @@ -33,7 +33,7 @@ #include #include #include -#include "../numeric_utils.hpp" +//#include "../numeric_utils.hpp" namespace xlnt { namespace detail { diff --git a/source/detail/serialization/xlsx_consumer.cpp b/source/detail/serialization/xlsx_consumer.cpp index 18504ff1..698b1af2 100644 --- a/source/detail/serialization/xlsx_consumer.cpp +++ b/source/detail/serialization/xlsx_consumer.cpp @@ -44,24 +44,33 @@ #include #include -namespace std { - -/// -/// Allows xml::qname to be used as a key in a std::unordered_map. -/// -template <> -struct hash -{ - std::size_t operator()(const xml::qname &k) const - { - static std::hash hasher; - return hasher(k.string()); - } -}; - -} // namespace std - namespace { +/// string_equal +/// for comparison between std::string and string literals +/// improves on std::string::operator==(char*) by knowing the length ahead of time +template +inline bool string_arr_loop_equal(const std::string &lhs, const char (&rhs)[N]) +{ + for (size_t i = 0; i < N - 1; ++i) + { + if (lhs[i] != rhs[i]) + { + return false; + } + } + return true; +} + +template +inline bool string_equal(const std::string &lhs, const char (&rhs)[N]) +{ + if (lhs.size() != N - 1) + { + return false; + } + // split function to assist with inlining of the size check + return string_arr_loop_equal(lhs, rhs); +} xml::qname &qn(const std::string &namespace_, const std::string &name) { @@ -107,25 +116,6 @@ bool is_true(const std::string &bool_string) #endif } -struct number_converter -{ - number_converter() - { - stream.imbue(std::locale("C")); - } - - double stold(const std::string &s) - { - stream.str(s); - stream.clear(); - stream >> result; - return result; - } - - std::istringstream stream; - double result; -}; - using style_id_pair = std::pair; /// @@ -143,6 +133,302 @@ 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; +}; + +xlnt::cell::type type_from_string(const std::string &str) +{ + if (string_equal(str, "s")) + { + return xlnt::cell::type::shared_string; + } + else if (string_equal(str, "n")) + { + return xlnt::cell::type::number; + } + else if (string_equal(str, "b")) + { + return xlnt::cell::type::boolean; + } + else if (string_equal(str, "e")) + { + return xlnt::cell::type::error; + } + else if (string_equal(str, "inlineStr")) + { + return xlnt::cell::type::inline_string; + } + else if (string_equal(str, "str")) + { + return xlnt::cell::type::formula_string; + } + return xlnt::cell::type::shared_string; +} + +Cell parse_cell(xlnt::row_t row_arg, xml::parser *parser) +{ + Cell c; + for (auto &attr : parser->attribute_map()) + { + if (string_equal(attr.first.name(), "r")) + { + c.ref = Cell_Reference(row_arg, attr.second.value); + } + else if (string_equal(attr.first.name(), "t")) + { + c.type = type_from_string(attr.second.value); + } + else if (string_equal(attr.first.name(), "s")) + { + c.style_index = static_cast(strtol(attr.second.value.c_str(), nullptr, 10)); + } + else if (string_equal(attr.first.name(), "ph")) + { + c.is_phonetic = is_true(attr.second.value); + } + else if (string_equal(attr.first.name(), "cm")) + { + c.cell_metatdata_idx = static_cast(strtol(attr.second.value.c_str(), nullptr, 10)); + } + } + int level = 1; // nesting level + // 1 == + // 2 == // + // exit loop at + while (level > 0) + { + xml::parser::event_type e = parser->next(); + switch (e) + { + case xml::parser::start_element: + { + ++level; + break; + } + case xml::parser::end_element: + { + --level; + break; + } + case xml::parser::characters: + { + // only want the characters inside one of the nested tags + // without this a lot of formatting whitespace can get added + if (level == 2) + { + // -> numeric values + // -> inline string + if (string_equal(parser->name(), "v") || string_equal(parser->name(), "is")) + { + c.value += std::move(parser->value()); + } + // formula + else if (string_equal(parser->name(), "f")) + { + c.formula_string += std::move(parser->value()); + } + } + break; + } + case xml::parser::start_namespace_decl: + case xml::parser::end_namespace_decl: + case xml::parser::start_attribute: + case xml::parser::end_attribute: + case xml::parser::eof: + default: + { + throw xlnt::exception("unexcpected XML parsing event"); + } + } + } + return c; +} + +// inside element +std::pair parse_row(xml::parser *parser, xlnt::detail::number_converter &converter, std::vector &parsed_cells) +{ + std::pair props; + for (auto &attr : parser->attribute_map()) + { + if (string_equal(attr.first.name(), "dyDescent")) + { + props.first.dy_descent = converter.stold(attr.second.value); + } + else if (string_equal(attr.first.name(), "spans")) + { + props.first.spans = attr.second.value; + } + else if (string_equal(attr.first.name(), "ht")) + { + props.first.height = converter.stold(attr.second.value); + } + else if (string_equal(attr.first.name(), "s")) + { + props.first.style = strtoul(attr.second.value.c_str(), nullptr, 10); + } + else if (string_equal(attr.first.name(), "hidden")) + { + props.first.hidden = is_true(attr.second.value); + } + else if (string_equal(attr.first.name(), "customFormat")) + { + props.first.custom_format = is_true(attr.second.value); + } + else if (string_equal(attr.first.name(), "ph")) + { + is_true(attr.second.value); + } + else if (string_equal(attr.first.name(), "r")) + { + props.second = static_cast(strtol(attr.second.value.c_str(), nullptr, 10)); + } + else if (string_equal(attr.first.name(), "customHeight")) + { + props.first.custom_height = is_true(attr.second.value.c_str()); + } + } + + int level = 1; + while (level > 0) + { + xml::parser::event_type e = parser->next(); + switch (e) + { + case xml::parser::start_element: + { + parsed_cells.push_back(parse_cell(props.second, parser)); + break; + } + case xml::parser::end_element: + { + --level; + break; + } + case xml::parser::characters: + { + // ignore whitespace + break; + } + case xml::parser::start_namespace_decl: + case xml::parser::start_attribute: + case xml::parser::end_namespace_decl: + case xml::parser::end_attribute: + case xml::parser::eof: + default: + { + throw xlnt::exception("unexcpected XML parsing event"); + } + } + } + return props; +} + +// inside element +Sheet_Data parse_sheet_data(xml::parser *parser, xlnt::detail::number_converter &converter) +{ + Sheet_Data sheet_data; + int level = 1; // nesting level + // 1 == + // 2 == + + while (level > 0) + { + xml::parser::event_type e = parser->next(); + switch (e) + { + case xml::parser::start_element: + { + sheet_data.parsed_rows.push_back(parse_row(parser, converter, sheet_data.parsed_cells)); + break; + } + case xml::parser::end_element: + { + --level; + break; + } + case xml::parser::characters: + { + // ignore, whitespace formatting normally + break; + } + case xml::parser::start_namespace_decl: + case xml::parser::start_attribute: + case xml::parser::end_namespace_decl: + case xml::parser::end_attribute: + case xml::parser::eof: + default: + { + throw xlnt::exception("unexcpected XML parsing event"); + } + } + } + return sheet_data; +} + } // namespace /* @@ -204,7 +490,7 @@ cell xlsx_consumer::read_cell() if (parser().attribute_present("ht")) { - row_properties.height = parser().attribute("ht"); + row_properties.height = converter_.stold(parser().attribute("ht")); } if (parser().attribute_present("customHeight")) @@ -219,10 +505,11 @@ cell xlsx_consumer::read_cell() if (parser().attribute_present(qn("x14ac", "dyDescent"))) { - row_properties.dy_descent = parser().attribute(qn("x14ac", "dyDescent")); + row_properties.dy_descent = converter_.stold(parser().attribute(qn("x14ac", "dyDescent"))); } - if (parser().attribute_present("spans")) { + if (parser().attribute_present("spans")) + { row_properties.spans = parser().attribute("spans"); } @@ -310,8 +597,6 @@ cell xlsx_consumer::read_cell() cell.formula(formula_value_string); } - number_converter converter; - if (has_value) { if (type == "str") @@ -326,7 +611,7 @@ cell xlsx_consumer::read_cell() } else if (type == "s") { - cell.d_->value_numeric_ = converter.stold(value_string); + cell.d_->value_numeric_ = converter_.stold(value_string); cell.data_type(cell::type::shared_string); } else if (type == "b") // boolean @@ -335,7 +620,7 @@ cell xlsx_consumer::read_cell() } else if (type == "n") // numeric { - cell.value(converter.stold(value_string)); + cell.value(converter_.stold(value_string)); } else if (!value_string.empty() && value_string[0] == '#') { @@ -587,23 +872,23 @@ std::string xlsx_consumer::read_worksheet_begin(const std::string &rel_id) if (parser().attribute_present("baseColWidth")) { ws.d_->format_properties_.base_col_width = - parser().attribute("baseColWidth"); + converter_.stold(parser().attribute("baseColWidth")); } if (parser().attribute_present("defaultColWidth")) { ws.d_->format_properties_.default_column_width = - parser().attribute("defaultColWidth"); + converter_.stold(parser().attribute("defaultColWidth")); } if (parser().attribute_present("defaultRowHeight")) { ws.d_->format_properties_.default_row_height = - parser().attribute("defaultRowHeight"); + converter_.stold(parser().attribute("defaultRowHeight")); } if (parser().attribute_present(qn("x14ac", "dyDescent"))) { ws.d_->format_properties_.dy_descent = - parser().attribute(qn("x14ac", "dyDescent")); + converter_.stold(parser().attribute(qn("x14ac", "dyDescent"))); } skip_attributes(); @@ -620,10 +905,10 @@ std::string xlsx_consumer::read_worksheet_begin(const std::string &rel_id) auto max = static_cast(std::stoull(parser().attribute("max"))); // avoid uninitialised warnings in GCC by using a lambda to make the conditional initialisation - optional width = [](xml::parser &p) -> xlnt::optional { + optional width = [this](xml::parser &p) -> xlnt::optional { if (p.attribute_present("width")) { - return (p.attribute("width") * 7 - 5) / 7; + return (converter_.stold(p.attribute("width")) * 7 - 5) / 7; } return xlnt::optional(); }(parser()); @@ -682,163 +967,76 @@ std::string xlsx_consumer::read_worksheet_begin(const std::string &rel_id) void xlsx_consumer::read_worksheet_sheetdata() { - auto ws = worksheet(current_worksheet_); - if (stack_.back() != qn("spreadsheetml", "sheetData")) { return; } - - number_converter converter; - - while (in_element(qn("spreadsheetml", "sheetData"))) + Sheet_Data ws_data = parse_sheet_data(parser_, converter_); + // NOTE: parse->construct are seperated here and could easily be threaded + // with a SPSC queue for what is likely to be an easy performance win + for (auto &row : ws_data.parsed_rows) { - expect_start_element(qn("spreadsheetml", "row"), xml::content::complex); // CT_Row - auto row_index = parser().attribute("r"); - auto &row_properties = ws.row_properties(row_index); - - if (parser().attribute_present("ht")) - { - row_properties.height = parser().attribute("ht"); - } - - if (parser().attribute_present("customHeight")) - { - row_properties.custom_height = is_true(parser().attribute("customHeight")); - } - - if (parser().attribute_present("hidden") && is_true(parser().attribute("hidden"))) - { - row_properties.hidden = true; - } - - if (parser().attribute_present(qn("x14ac", "dyDescent"))) - { - row_properties.dy_descent = parser().attribute(qn("x14ac", "dyDescent")); - } - - if (parser().attribute_present("s")) - { - row_properties.style.set(static_cast(std::stoull(parser().attribute("s")))); - } - if (parser().attribute_present("customFormat")) - { - row_properties.custom_format.set(parser().attribute("customFormat")); - } - - if (parser().attribute_present("spans")) { - row_properties.spans = parser().attribute("spans"); - } - - skip_attributes({"customFont", - "outlineLevel", "collapsed", "thickTop", "thickBot", - "ph"}); - - while (in_element(qn("spreadsheetml", "row"))) - { - expect_start_element(qn("spreadsheetml", "c"), xml::content::complex); - auto cell = ws.cell(cell_reference(parser().attribute("r"))); - - auto has_type = parser().attribute_present("t"); - auto type = has_type ? parser().attribute("t") : "n"; - - if (parser().attribute_present("s")) - { - cell.format(target_.format(static_cast(std::stoull(parser().attribute("s"))))); - } - - if (parser().attribute_present("ph")) - { - cell.d_->phonetics_visible_ = parser().attribute("ph"); - } - - auto has_value = false; - auto value_string = std::string(); - - auto has_formula = false; - auto has_shared_formula = false; - auto formula_value_string = std::string(); - - while (in_element(qn("spreadsheetml", "c"))) - { - auto current_element = expect_start_element(xml::content::mixed); - - if (current_element == qn("spreadsheetml", "v")) // s:ST_Xstring - { - has_value = true; - value_string = read_text(); - } - else if (current_element == qn("spreadsheetml", "f")) // CT_CellFormula - { - has_formula = true; - - if (parser().attribute_present("t")) - { - has_shared_formula = parser().attribute("t") == "shared"; - } - - skip_attributes( - {"aca", "ref", "dt2D", "dtr", "del1", "del2", "r1", "r2", "ca", "si", "bx"}); - - formula_value_string = read_text(); - } - else if (current_element == qn("spreadsheetml", "is")) // CT_Rst - { - expect_start_element(qn("spreadsheetml", "t"), xml::content::simple); - value_string = read_text(); - expect_end_element(qn("spreadsheetml", "t")); - } - else - { - unexpected_element(current_element); - } - - expect_end_element(current_element); - } - - expect_end_element(qn("spreadsheetml", "c")); - - if (has_formula && !has_shared_formula) - { - cell.formula(formula_value_string); - } - - if (has_value) - { - if (type == "str") - { - cell.d_->value_text_ = value_string; - cell.data_type(cell::type::formula_string); - } - else if (type == "inlineStr") - { - cell.d_->value_text_ = value_string; - cell.data_type(cell::type::inline_string); - } - else if (type == "s") - { - cell.d_->value_numeric_ = converter.stold(value_string); - cell.data_type(cell::type::shared_string); - } - else if (type == "b") // boolean - { - cell.value(is_true(value_string)); - } - else if (type == "n") // numeric - { - cell.value(converter.stold(value_string)); - } - else if (!value_string.empty() && value_string[0] == '#') - { - cell.error(value_string); - } - } - } - - expect_end_element(qn("spreadsheetml", "row")); + current_worksheet_->row_properties_.emplace(row.second, std::move(row.first)); } - - expect_end_element(qn("spreadsheetml", "sheetData")); + auto impl = detail::cell_impl(); + for (Cell &cell : ws_data.parsed_cells) + { + impl.parent_ = current_worksheet_; + impl.column_ = cell.ref.column; + impl.row_ = cell.ref.row; + detail::cell_impl *ws_cell_impl = ¤t_worksheet_->cell_map_.emplace(cell_reference(impl.column_, impl.row_), std::move(impl)).first->second; + if (cell.style_index != -1) + { + ws_cell_impl->format_ = target_.format(static_cast(cell.style_index)).d_; + } + if (cell.cell_metatdata_idx != -1) + { + } + ws_cell_impl->phonetics_visible_ = cell.is_phonetic; + if (!cell.formula_string.empty()) + { + ws_cell_impl->formula_ = cell.formula_string[0] == '=' ? cell.formula_string.substr(1) : std::move(cell.formula_string); + } + if (!cell.value.empty()) + { + ws_cell_impl->type_ = cell.type; + switch (cell.type) + { + case cell::type::boolean: + { + ws_cell_impl->value_numeric_ = is_true(cell.value) ? 1.0 : 0.0; + break; + } + case cell::type::empty: + case cell::type::number: + { + ws_cell_impl->value_numeric_ = converter_.stold(cell.value); + break; + } + case cell::type::shared_string: + { + ws_cell_impl->value_numeric_ = static_cast(strtol(cell.value.c_str(), nullptr, 10)); + break; + } + case cell::type::inline_string: + { + ws_cell_impl->value_text_ = std::move(cell.value); + break; + } + case cell::type::formula_string: + { + ws_cell_impl->value_text_ = std::move(cell.value); + break; + } + case cell::type::error: + { + ws_cell_impl->value_text_.plain_text(cell.value, false); + break; + } + } + } + } + stack_.pop_back(); } worksheet xlsx_consumer::read_worksheet_end(const std::string &rel_id) @@ -1012,12 +1210,12 @@ worksheet xlsx_consumer::read_worksheet_end(const std::string &rel_id) { page_margins margins; - margins.top(parser().attribute("top")); - margins.bottom(parser().attribute("bottom")); - margins.left(parser().attribute("left")); - margins.right(parser().attribute("right")); - margins.header(parser().attribute("header")); - margins.footer(parser().attribute("footer")); + margins.top(converter_.stold(parser().attribute("top"))); + margins.bottom(converter_.stold(parser().attribute("bottom"))); + margins.left(converter_.stold(parser().attribute("left"))); + margins.right(converter_.stold(parser().attribute("right"))); + margins.header(converter_.stold(parser().attribute("header"))); + margins.footer(converter_.stold(parser().attribute("footer"))); ws.page_margins(margins); } @@ -2124,7 +2322,7 @@ void xlsx_consumer::read_stylesheet() while (in_element(qn("spreadsheetml", "gradientFill"))) { expect_start_element(qn("spreadsheetml", "stop"), xml::content::complex); - auto position = parser().attribute("position"); + auto position = converter_.stold(parser().attribute("position")); expect_start_element(qn("spreadsheetml", "color"), xml::content::complex); auto color = read_color(); expect_end_element(qn("spreadsheetml", "color")); @@ -2172,7 +2370,7 @@ void xlsx_consumer::read_stylesheet() if (font_property_element == qn("spreadsheetml", "sz")) { - new_font.size(parser().attribute("val")); + new_font.size(converter_.stold(parser().attribute("val"))); } else if (font_property_element == qn("spreadsheetml", "name")) { @@ -2741,10 +2939,10 @@ void xlsx_consumer::read_drawings(worksheet ws, const path &part) auto sd = drawing::spreadsheet_drawing(parser()); - for (const auto image_rel_id : sd.get_embed_ids()) + for (const auto &image_rel_id : sd.get_embed_ids()) { auto image_rel = std::find_if(images.begin(), images.end(), - [&](const relationship &r) { return r.id() == image_rel_id; }); + [&](const relationship &r) { return r.id() == image_rel_id; }); if (image_rel != images.end()) { @@ -2976,7 +3174,7 @@ rich_text xlsx_consumer::read_rich_text(const xml::qname &parent) if (current_run_property_element == xml::qname(xmlns, "sz")) { - run.second.get().size(parser().attribute("val")); + run.second.get().size(converter_.stold(parser().attribute("val"))); } else if (current_run_property_element == xml::qname(xmlns, "rFont")) { @@ -3114,7 +3312,7 @@ xlnt::color xlsx_consumer::read_color() if (parser().attribute_present("tint")) { - result.tint(parser().attribute("tint")); + result.tint(converter_.stold(parser().attribute("tint"))); } return result; diff --git a/source/detail/serialization/xlsx_consumer.hpp b/source/detail/serialization/xlsx_consumer.hpp index fd6d70fb..c1c36006 100644 --- a/source/detail/serialization/xlsx_consumer.hpp +++ b/source/detail/serialization/xlsx_consumer.hpp @@ -34,6 +34,7 @@ #include #include +#include namespace xlnt { @@ -409,6 +410,7 @@ private: detail::cell_impl *current_cell_; detail::worksheet_impl *current_worksheet_; + number_converter converter_; }; } // namespace detail diff --git a/source/detail/serialization/xlsx_producer.cpp b/source/detail/serialization/xlsx_producer.cpp index 66c4860c..31e36347 100644 --- a/source/detail/serialization/xlsx_producer.cpp +++ b/source/detail/serialization/xlsx_producer.cpp @@ -33,10 +33,10 @@ #include #include #include -#include #include #include #include +#include #include #include #include diff --git a/source/worksheet/page_margins.cpp b/source/worksheet/page_margins.cpp index fc1c69c2..883d3ebe 100644 --- a/source/worksheet/page_margins.cpp +++ b/source/worksheet/page_margins.cpp @@ -22,7 +22,7 @@ // @license: http://www.opensource.org/licenses/mit-license.php // @author: see AUTHORS file #include -#include "detail/numeric_utils.hpp" +#include namespace xlnt { diff --git a/source/worksheet/page_setup.cpp b/source/worksheet/page_setup.cpp index 087d980c..2fab4113 100644 --- a/source/worksheet/page_setup.cpp +++ b/source/worksheet/page_setup.cpp @@ -22,7 +22,7 @@ // @license: http://www.opensource.org/licenses/mit-license.php // @author: see AUTHORS file #include -#include "detail/numeric_utils.hpp" +#include namespace xlnt { diff --git a/source/worksheet/worksheet.cpp b/source/worksheet/worksheet.cpp index b0e41945..c2db52ee 100644 --- a/source/worksheet/worksheet.cpp +++ b/source/worksheet/worksheet.cpp @@ -46,7 +46,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/tests/detail/numeric_util_test_suite.cpp b/tests/detail/numeric_util_test_suite.cpp index 5fb033d1..51c67b98 100644 --- a/tests/detail/numeric_util_test_suite.cpp +++ b/tests/detail/numeric_util_test_suite.cpp @@ -21,7 +21,7 @@ // @license: http://www.opensource.org/licenses/mit-license.php // @author: see AUTHORS file -#include "../../source/detail/numeric_utils.hpp" +#include #include class numeric_test_suite : public test_suite diff --git a/tests/workbook/serialization_test_suite.cpp b/tests/workbook/serialization_test_suite.cpp index 921ae37b..004dcc03 100644 --- a/tests/workbook/serialization_test_suite.cpp +++ b/tests/workbook/serialization_test_suite.cpp @@ -90,6 +90,7 @@ public: register_test(test_round_trip_rw_encrypted_numbers); register_test(test_streaming_read); register_test(test_streaming_write); + register_test(test_load_save_german_locale); } bool workbook_matches_file(xlnt::workbook &wb, const xlnt::path &file) @@ -708,5 +709,12 @@ public: b2.value("should not change"); c3.value("C3!"); } + + void test_load_save_german_locale() + { + /* std::locale current(std::locale::global(std::locale("de-DE"))); + test_round_trip_rw_custom_heights_widths(); + std::locale::global(current);*/ + } }; static serialization_test_suite x;