From 1bd1ec1b19e65fd2d65ab0a0aec5157948d87743 Mon Sep 17 00:00:00 2001 From: David van Erkelens Date: Thu, 26 Mar 2020 11:40:26 +0100 Subject: [PATCH] Extend number format (#19) Add custom decimal and thousands separarators --- src/builtin/number_format.h | 76 ++++++++++++++++++++++++++++--------- test/modifier.cpp | 19 ++++++++++ 2 files changed, 78 insertions(+), 17 deletions(-) diff --git a/src/builtin/number_format.h b/src/builtin/number_format.h index 58e1361..451c3e4 100644 --- a/src/builtin/number_format.h +++ b/src/builtin/number_format.h @@ -4,9 +4,14 @@ * Built-in "|number_format" modifier * * @author David van Erkelens - * @copyright 2019 Copernica BV + * @copyright 2019 - 2020 Copernica BV */ +/** + * Dependencies + */ +#include + /** * Namespace */ @@ -17,6 +22,25 @@ namespace SmartTpl { namespace Internal { */ class NumberFormatModifier : public Modifier { +private: + /** + * Struct that works with the std::numpunct facet and can be + * constructed with a char for the decimal and thousands separators + */ + struct formatter : std::numpunct + { + // chars for the separators + char decimal; char thousand; + + // constructor + formatter(char decimal, char thousand) : decimal(decimal), thousand(thousand) {} + + // build in functions for separator formatting + char do_thousands_sep() const { return thousand; } // thousands separator + std::string do_grouping() const { return (int) thousand == 0 ? "" : "\3"; } // separate every 3 digits, if we have a separator + char do_decimal_point() const { return decimal; } // decimal separator + }; + public: /** * Destructor @@ -31,32 +55,50 @@ class NumberFormatModifier : public Modifier */ VariantValue modify(const Value &input, const SmartTpl::Parameters ¶ms) override { - // Convert input to double - double original(input.toDouble()); - - // make sure we have a parameter containing the format + // make sure we have a parameter containing the number of decimals if (params.size() < 1) throw NoModification(); - // get the amound of decimals to output + // get the amount of decimals to output int decimals = params[0].toInteger(); - // buffer to create the printf format - char format[10]; + // get separator variables for decimals and thousands + char decimal_separator = '.'; char thousand_separator = (char) 0; + + // if we have a valid parameter, overwrite decimal separator + if (params.size() > 1) + { + // convert to string + auto param = params[1].toString(); + + // make sure that we have a character (we could throw if the param is too long?) + if (param.size() > 0) decimal_separator = param[0]; + } + + // if we have a valid parameter, overwrite thousands separator + if (params.size() > 2) + { + // convert to string + auto param = params[2].toString(); + + // make sure that we have a character (we could throw if the param is too long?) + if (param.size() > 0) thousand_separator = param[0]; + } - // format the format - sprintf(format, "%%.%if", decimals); + // create stringstream to store formatted number + std::stringstream stream; - // calculate size of new string - size_t size = snprintf(nullptr, 0, format, original); + // create custom locale for our formatting options + std::locale formatting_locale(stream.getloc(), new NumberFormatModifier::formatter(decimal_separator, thousand_separator)); - // create buffer - char buffer[size + 1]; + // set formatting options + stream.precision(decimals); + stream.imbue(formatting_locale); - // create new string - sprintf(buffer, format, original); + // stream the value (never in scientific format) + stream << std::fixed << input.toDouble(); // create object - return VariantValue(buffer); + return VariantValue(stream.str()); } }; diff --git a/test/modifier.cpp b/test/modifier.cpp index 3e0eab2..3a14ac5 100644 --- a/test/modifier.cpp +++ b/test/modifier.cpp @@ -359,6 +359,25 @@ TEST(Modifier, NumberFormat) } } +TEST(Modifier, NumberFormatWithSeparators) +{ + string input("{$var1|number_format:2:'.':','}\n{$var1|number_format:2:',':'|'}\n{$var1|number_format:0:'.':','}\n{$var1|number_format:2:'x'}"); + Template tpl((Buffer(input))); + + Data data; + data.assign("var1", "123123.45"); + + string expectedOutput("123,123.45\n123|123,45\n123,123\n123123x45"); + + EXPECT_EQ(expectedOutput, tpl.process(data)); + + if (compile(tpl)) // This will compile the Template into a shared library + { + Template library(File(SHARED_LIBRARY)); // Here we load that shared library + EXPECT_EQ(expectedOutput, library.process(data)); + } +} + TEST(Modifier, Count) { string input("{$var|count}");