Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
[core] Add number-format expression
Browse files Browse the repository at this point in the history
  • Loading branch information
Juha Alanen committed Jun 10, 2019
1 parent 892699a commit 7027e47
Show file tree
Hide file tree
Showing 20 changed files with 486 additions and 5 deletions.
3 changes: 2 additions & 1 deletion include/mbgl/style/expression/expression.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ enum class Kind : int32_t {
All,
Comparison,
FormatExpression,
FormatSectionOverride
FormatSectionOverride,
NumberFormat
};

class Expression {
Expand Down
38 changes: 38 additions & 0 deletions include/mbgl/style/expression/number_format.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#pragma once

#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/expression/parsing_context.hpp>

namespace mbgl {
namespace style {
namespace expression {

class NumberFormat final : public Expression {
public:
NumberFormat(std::unique_ptr<Expression> number_,
optional<std::unique_ptr<Expression>> locale_,
optional<std::unique_ptr<Expression>> currency_,
optional<std::unique_ptr<Expression>> minFractionDigits_,
optional<std::unique_ptr<Expression>> maxFractionDigits_);

static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);

EvaluationResult evaluate(const EvaluationContext& params) const override;
void eachChild(const std::function<void(const Expression&)>& visit) const override;
bool operator==(const Expression& e) const override;
std::vector<optional<Value>> possibleOutputs() const override;

mbgl::Value serialize() const override;
std::string getOperator() const override { return "number-format"; }

private:
std::unique_ptr<Expression> number;
std::unique_ptr<Expression> locale;
std::unique_ptr<Expression> currency;
std::unique_ptr<Expression> minFractionDigits;
std::unique_ptr<Expression> maxFractionDigits;
};

} // namespace expression
} // namespace style
} // namespace mbgl
6 changes: 6 additions & 0 deletions include/mbgl/util/platform.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ std::string lowercase(const std::string &string);
// Gets the name of the current thread.
std::string getCurrentThreadName();

std::string formatNumber(double number,
const std::string& localeId,
const std::string& currency,
uint8_t minFractionDigits,
uint8_t maxFractionDigits);

// Set the name of the current thread, truncated at 15.
void setCurrentThreadName(const std::string& name);

Expand Down
21 changes: 21 additions & 0 deletions platform/android/src/string_util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,26 @@ std::string lowercase(const std::string& str) {
return jni::Make<std::string>(*env, result);
}

std::string formatNumber(double number,
const std::string& localeId,
const std::string& currency,
uint8_t minFractionDigits,
uint8_t maxFractionDigits)
{
(void)number;
(void)localeId;
(void)currency;
(void)minFractionDigits;
(void)maxFractionDigits;

auto env{ android::AttachEnv() };
jni::Local<jni::String> localeStr = jni::Make<jni::String>(*env, localeId.c_str());

static auto toLowerCase = jni::Class<jni::StringTag>::Singleton(*env).GetMethod<jni::String()>(*env, "toLowerCase");
jni::Local<jni::String> value = jni::Make<jni::String>(*env, localeId.c_str());
auto result = value.Call(*env, toLowerCase);
return jni::Make<std::string>(*env, result);
}

} // namespace platform
} // namespace mbgl
20 changes: 20 additions & 0 deletions platform/darwin/src/string_nsstring.mm
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,25 @@
return result;
}

std::string formatNumber(double number,
const std::string& localeId,
const std::string& currency,
uint8_t minFractionDigits,
uint8_t maxFractionDigits)
{
NSNumberFormatter *_numberFormatter;
_numberFormatter = [[NSNumberFormatter alloc] init];
_numberFormatter.locale = !localeId.empty() ? [NSLocale localeWithLocaleIdentifier:@(localeId.c_str())] : nil;
_numberFormatter.currencyCode = !currency.empty() ? @(currency.c_str()) : nil;
if (currency.empty()) {
_numberFormatter.minimumFractionDigits = minFractionDigits;
_numberFormatter.maximumFractionDigits = maxFractionDigits;
}
_numberFormatter.numberStyle = !currency.empty() ? NSNumberFormatterCurrencyStyle : NSNumberFormatterDecimalStyle;
NSString *formatted = [_numberFormatter stringFromNumber:@(number)];
std::string result = std::string([formatted UTF8String]);
return result;
}

}
}
98 changes: 98 additions & 0 deletions platform/default/src/mbgl/util/string_stdlib.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
#include <mbgl/util/platform.hpp>
#include <mbgl/util/string.hpp>
#include <libnu/casemap.h>
#include <cstring>
#include <sstream>
#include <locale>
#include <iomanip>
#include <cmath>
#include <stdexcept>

namespace mbgl { namespace platform {

Expand Down Expand Up @@ -70,5 +75,98 @@ std::string lowercase(const std::string& str)
return output.str();
}

std::string formatNumber(double number,
const std::string& localeId,
const std::string& currency,
uint8_t minFractionDigits,
uint8_t maxFractionDigits)
{
std::stringstream output;
std::locale loc;
bool localeSet;

// Replace '-' with '_' in the locale string and append '.utf8' to it
std::string localeStr = localeId;
if (!localeStr.empty()) {
localeStr.replace(localeStr.find_first_of("-"), 1, "_");
localeStr.append(".utf8");
}

try {
loc = std::locale(localeStr.c_str());
localeSet = true;
}
catch (std::runtime_error& e) {
loc = std::locale("");
localeSet = false;
}
output.imbue(loc);

// Print the value as currency
if (!currency.empty()) {
// If locale was successfully set print the input value formatted as money and
// with currency symbol, otherwise print the input value with currency string.
if (localeSet) {
const auto& mp = std::use_facet<std::moneypunct<char>>(loc);
int fracDigits = mp.frac_digits();
// Multiply with 10^fracDigits to print the value correctly
if (fracDigits) {
number = number * pow(10, fracDigits);
}
output << std::showbase << std::put_money(number);
} else {
output << currency << " " << util::toString(number);
}
} else {
unsigned int minPrecision = 0, maxPrecision = 3; //defaults
auto integerPart = static_cast<long>(number);
std::string fractPartStr;

if (minFractionDigits > 0) {
minPrecision = minFractionDigits;
}
if (maxFractionDigits > 0) {
maxPrecision = maxFractionDigits;
}

if (maxPrecision != 0) {
double fractPart = number - integerPart;
fractPartStr = std::to_string(fractPart).substr(2, std::string::npos);
// Erase possible trailing zeros in the decimal part
fractPartStr.erase(fractPartStr.find_last_not_of('0') + 1, std::string::npos);
std::size_t fractLen = fractPartStr.length();

auto numDecimals = static_cast<unsigned int>(fractLen);
if (numDecimals < minPrecision) {
// Append extra zeroes to the decimal part in case it is too short
fractPartStr.append(minPrecision - fractLen, '0');
} else if (numDecimals > maxPrecision) {
// Shorten the decimal part to the requested precision in case it is too long
fractPart = round(fractPart * pow(10, maxPrecision));
fractPart = fractPart / pow(10, maxPrecision);
fractPartStr = std::to_string(fractPart).substr(2, std::string::npos);
fractPartStr.resize(maxPrecision);

// Increment the integer part if the fractional part rounds up to 1
if (fractPart >= 1) {
integerPart++;
}
}
char point = std::use_facet<std::numpunct<char>>(loc).decimal_point();
std::string pointStr = std::string(&point, 1);
fractPartStr.insert(0, pointStr);
} else {
// Round the value to the nearest integer in case no decimals are requested
integerPart = round(number);
}
output << integerPart << fractPartStr;
}

if (output.good()) {
return output.str();
}
return std::string("Could not evaluate number-format input to string");
}

} // namespace platform
} // namespace mbgl
3 changes: 2 additions & 1 deletion platform/node/src/node_expression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ type::Type parseType(v8::Local<v8::Object> type) {
{"object", type::Object},
{"color", type::Color},
{"value", type::Value},
{"formatted", type::Formatted}
{"formatted", type::Formatted},
{"number-format", type::String}
};

v8::Local<v8::Value> v8kind = Nan::Get(type, Nan::New("kind").ToLocalChecked()).ToLocalChecked();
Expand Down
1 change: 1 addition & 0 deletions platform/qt/config.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ if (NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
PRIVATE platform/qt/test/main.cpp
PRIVATE platform/qt/test/qmapboxgl.test.cpp
PRIVATE platform/qt/test/qmapboxgl.test.cpp
PRIVATE platform/qt/resources/common.qrc
)

target_include_directories(mbgl-test
Expand Down
4 changes: 4 additions & 0 deletions platform/qt/qt.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ find_package(Qt5Network REQUIRED)
find_package(Qt5OpenGL REQUIRED)
find_package(Qt5Widgets REQUIRED)
find_package(Qt5Sql REQUIRED)
find_package(Qt5Qml REQUIRED)
find_package(Qt5Quick REQUIRED)

# Qt5 always build OpenGL ES2 which is the compatibility
# mode. Qt5 will take care of translating the desktop
Expand All @@ -125,6 +127,8 @@ set(MBGL_QT_CORE_LIBRARIES
PUBLIC Qt5::Core
PUBLIC Qt5::Gui
PUBLIC Qt5::OpenGL
PUBLIC Qt5::Qml
PUBLIC Qt5::Quick
)

set(MBGL_QT_FILESOURCE_LIBRARIES
Expand Down
1 change: 1 addition & 0 deletions platform/qt/resources/common.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
<file>source2.geojson</file>
<file>label-arrow.svg</file>
<file>label-background.svg</file>
<file alias="FormatNumber.qml">../../../platform/qt/src/FormatNumber.qml</file>
</qresource>
</RCC>
11 changes: 11 additions & 0 deletions platform/qt/src/FormatNumber.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import QtQuick 2.0

Item {
function formatNumber(number, localeId, currency, minFractionDigits, maxFractionDigits) {
if (currency !== "undefined") {
return Number(number).toLocaleCurrencyString(Qt.locale(localeId))
} else {
return Number(number).toLocaleString(Qt.locale(localeId), 'f', maxFractionDigits)
}
}
}
27 changes: 27 additions & 0 deletions platform/qt/src/string_stdlib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <QByteArray>
#include <QString>

#include <QtQml/QtQml>
#include <string>

namespace mbgl {
Expand All @@ -20,5 +21,31 @@ std::string lowercase(const std::string& str) {
return std::string(lower.constData(), lower.size());
}

std::string formatNumber(double number,
const std::string& localeId,
const std::string& currency,
uint8_t minFractionDigits,
uint8_t maxFractionDigits)
{
std::string formatted;
QQmlEngine engine;
QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/FormatNumber.qml")));
QObject *object = component.create();

QVariant returnedValue;
QVariant qNumber = number;
QVariant qLocale = QString::fromStdString(localeId);
QVariant qCurrency = QString::fromStdString(currency);
QVariant qMinFractionDigits = minFractionDigits;
QVariant qMaxFractionDigits = maxFractionDigits;
QMetaObject::invokeMethod(object, "formatNumber", Q_RETURN_ARG(QVariant, returnedValue),
Q_ARG(QVariant, qNumber), Q_ARG(QVariant, qLocale), Q_ARG(QVariant, qCurrency),
Q_ARG(QVariant, qMinFractionDigits), Q_ARG(QVariant, qMaxFractionDigits));

formatted = returnedValue.toString().toStdString();
delete object;
return formatted;
}

} // namespace platform
} // namespace mbgl
2 changes: 2 additions & 0 deletions src/core-files.json
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@
"src/mbgl/style/expression/let.cpp",
"src/mbgl/style/expression/literal.cpp",
"src/mbgl/style/expression/match.cpp",
"src/mbgl/style/expression/number_format.cpp",
"src/mbgl/style/expression/parsing_context.cpp",
"src/mbgl/style/expression/step.cpp",
"src/mbgl/style/expression/util.cpp",
Expand Down Expand Up @@ -414,6 +415,7 @@
"mbgl/style/expression/let.hpp": "include/mbgl/style/expression/let.hpp",
"mbgl/style/expression/literal.hpp": "include/mbgl/style/expression/literal.hpp",
"mbgl/style/expression/match.hpp": "include/mbgl/style/expression/match.hpp",
"mbgl/style/expression/number_format.hpp": "include/mbgl/style/expression/number_format.hpp",
"mbgl/style/expression/parsing_context.hpp": "include/mbgl/style/expression/parsing_context.hpp",
"mbgl/style/expression/step.hpp": "include/mbgl/style/expression/step.hpp",
"mbgl/style/expression/type.hpp": "include/mbgl/style/expression/type.hpp",
Expand Down
Loading

0 comments on commit 7027e47

Please sign in to comment.