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)