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
pull/467/head
JCrawfy 2020-04-25 14:00:58 +12:00
parent dfb8f1518e
commit d30e705f83
6 changed files with 70 additions and 74 deletions

View File

@ -129,10 +129,12 @@ class number_serialiser
public: public:
explicit number_serialiser() explicit number_serialiser()
: should_convert_comma(std::use_facet<std::numpunct<char>>(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 std::string serialise(double d) const
{ {
char buf[30]; char buf[30];
@ -144,29 +146,43 @@ public:
return std::string(buf, static_cast<size_t>(len)); return std::string(buf, static_cast<size_t>(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) if (should_convert_comma)
{ {
// s.data() doesn't have a non-const overload until c++17, hence this little dance convert_comma_to_pt(buf, len);
convert_pt_to_comma(&s[0], s.size());
} }
return strtod(s.c_str(), nullptr); return std::string(buf, static_cast<size_t>(len));
} }
double deserialise(const std::string &s) const double deserialise(const std::string &s, ptrdiff_t *len_converted) const
{ {
assert(!s.empty()); assert(!s.empty());
assert(len_converted != nullptr);
char *end_of_convert;
if (!should_convert_comma) 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]; char buf[30];
assert(s.size() < sizeof(buf)); assert(s.size() < sizeof(buf));
auto copy_end = std::copy(s.begin(), s.end(), buf); auto copy_end = std::copy(s.begin(), s.end(), buf);
convert_pt_to_comma(buf, static_cast<size_t>(copy_end - buf)); convert_pt_to_comma(buf, static_cast<size_t>(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);
} }
}; };

View File

@ -57,15 +57,16 @@
#include <detail/implementations/hyperlink_impl.hpp> #include <detail/implementations/hyperlink_impl.hpp>
#include <detail/implementations/stylesheet.hpp> #include <detail/implementations/stylesheet.hpp>
#include <detail/implementations/worksheet_impl.hpp> #include <detail/implementations/worksheet_impl.hpp>
#include <xlnt/utils/numeric.hpp>
namespace { namespace {
std::pair<bool, double> cast_numeric(const std::string &s) std::pair<bool, double> cast_numeric(const std::string &s)
{ {
auto str_end = static_cast<char *>(nullptr); xlnt::detail::number_serialiser ser;
auto result = std::strtod(s.c_str(), &str_end); ptrdiff_t len_convert;
double result = ser.deserialise(s, &len_convert);
return (str_end != s.c_str() + s.size()) return (len_convert != static_cast<ptrdiff_t>(s.size()))
? std::make_pair(false, 0.0) ? std::make_pair(false, 0.0)
: std::make_pair(true, result); : std::make_pair(true, result);
} }
@ -108,7 +109,7 @@ std::pair<bool, xlnt::time> cast_time(const std::string &s)
} }
std::vector<double> numeric_components; std::vector<double> numeric_components;
xlnt::detail::number_serialiser ser;
for (auto component : time_components) for (auto component : time_components)
{ {
if (component.empty() || (component.substr(0, component.find('.')).size() > 2)) if (component.empty() || (component.substr(0, component.find('.')).size() > 2))
@ -123,9 +124,7 @@ std::pair<bool, xlnt::time> cast_time(const std::string &s)
return {false, result}; return {false, result};
} }
} }
auto numeric = ser.deserialise(component);
auto without_leading_zero = component.front() == '0' ? component.substr(1) : component;
auto numeric = std::stod(without_leading_zero);
numeric_components.push_back(numeric); numeric_components.push_back(numeric);
} }

View File

@ -26,6 +26,7 @@
#include <cmath> #include <cmath>
#include <xlnt/utils/exceptions.hpp> #include <xlnt/utils/exceptions.hpp>
#include <xlnt/utils/numeric.hpp>
#include <detail/default_case.hpp> #include <detail/default_case.hpp>
#include <detail/number_format/number_formatter.hpp> #include <detail/number_format/number_formatter.hpp>
@ -622,7 +623,8 @@ void number_format_parser::parse()
value = token.string.substr(1); value = token.string.substr(1);
} }
section.condition.value = std::stod(value); detail::number_serialiser ser;
section.condition.value = ser.deserialise(value);
break; break;
} }
@ -1565,19 +1567,7 @@ std::string number_formatter::fill_placeholders(const format_placeholders &p, do
if (p.type == format_placeholders::placeholders_type::general if (p.type == format_placeholders::placeholders_type::general
|| p.type == format_placeholders::placeholders_type::text) || p.type == format_placeholders::placeholders_type::text)
{ {
result = std::to_string(number); return serialiser_.serialise_short(number);
while (result.back() == '0')
{
result.pop_back();
}
if (result.back() == '.')
{
result.pop_back();
}
return result;
} }
if (p.percentage) if (p.percentage)
@ -1636,21 +1626,22 @@ std::string number_formatter::fill_placeholders(const format_placeholders &p, do
auto fractional_part = number - integer_part; auto fractional_part = number - integer_part;
result = std::fabs(fractional_part) < std::numeric_limits<double>::min() result = std::fabs(fractional_part) < std::numeric_limits<double>::min()
? std::string(".") ? 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)) while (result.back() == '0' || result.size() > (p.num_zeros + p.num_optionals + p.num_spaces + 1))
{ {
result.pop_back(); 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) 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'); integer_string = std::string(integer_part.num_zeros + integer_part.num_optionals, '0');
} }
std::string fractional_string = std::to_string(fraction).substr(1); std::string fractional_string = serialiser_.serialise_short(fraction).substr(1, fractional_part.num_zeros + fractional_part.num_optionals + 1);
while (fractional_string.size() > fractional_part.num_zeros + fractional_part.num_optionals + 1)
{
fractional_string.pop_back();
}
std::string exponent_string = std::to_string(logarithm); std::string exponent_string = std::to_string(logarithm);
while (exponent_string.size() < fractional_part.num_zeros) while (exponent_string.size() < fractional_part.num_zeros)

View File

@ -28,6 +28,7 @@
#include <vector> #include <vector>
#include <xlnt/utils/datetime.hpp> #include <xlnt/utils/datetime.hpp>
#include <xlnt/utils/numeric.hpp>
namespace xlnt { namespace xlnt {
namespace detail { namespace detail {
@ -691,6 +692,7 @@ private:
number_format_parser parser_; number_format_parser parser_;
std::vector<format_code> format_; std::vector<format_code> format_;
xlnt::calendar calendar_; xlnt::calendar calendar_;
xlnt::detail::number_serialiser serialiser_;
}; };
} // namespace detail } // namespace detail

View File

@ -2813,33 +2813,12 @@ void xlsx_producer::write_worksheet(const relationship &rel)
{ {
write_start_element(xmlns, "pageMargins"); write_start_element(xmlns, "pageMargins");
// TODO: there must be a better way to do this write_attribute("left", ws.page_margins().left());
auto remove_trailing_zeros = [](const std::string &n) -> std::string { write_attribute("right", ws.page_margins().right());
auto decimal = n.find('.'); write_attribute("top", ws.page_margins().top());
write_attribute("bottom", ws.page_margins().bottom());
if (decimal == std::string::npos) return n; write_attribute("header", ws.page_margins().header());
write_attribute("footer", ws.page_margins().footer());
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_end_element(xmlns, "pageMargins"); write_end_element(xmlns, "pageMargins");
} }

View File

@ -28,9 +28,9 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <xlnt/utils/numeric.hpp>
#include <detail/constants.hpp> #include <detail/constants.hpp>
#include <detail/external/include_libstudxml.hpp> #include <detail/external/include_libstudxml.hpp>
#include <xlnt/utils/numeric.hpp>
namespace xml { namespace xml {
class serializer; class serializer;
@ -169,19 +169,34 @@ private:
void write_namespace(const std::string &ns, const std::string &prefix); void write_namespace(const std::string &ns, const std::string &prefix);
template<typename T> // std::string attribute name
// not integer or float type
template <typename T, typename = std::enable_if_t<!std::is_convertible_v<T, double>>>
void write_attribute(const std::string &name, T value) void write_attribute(const std::string &name, T value)
{ {
current_part_serializer_->attribute(name, value); current_part_serializer_->attribute(name, value);
} }
template<typename T> 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 <typename T, typename = std::enable_if_t<!std::is_convertible_v<T, double>>>
void write_attribute(const xml::qname &name, T value) void write_attribute(const xml::qname &name, T value)
{ {
current_part_serializer_->attribute(name, value); current_part_serializer_->attribute(name, value);
} }
template<typename T> void write_attribute(const xml::qname &name, double value)
{
current_part_serializer_->attribute(name, converter_.serialise(value));
}
template <typename T>
void write_characters(T characters, bool preserve_whitespace = false) void write_characters(T characters, bool preserve_whitespace = false)
{ {
if (preserve_whitespace) if (preserve_whitespace)