summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJuha Alanen <juha.alanen@mapbox.com>2019-06-03 15:34:13 +0300
committerJuha Alanen <juha.alanen@mapbox.com>2019-06-27 14:44:13 +0300
commit50bdbea570ad079490f160a189010271bd33bcb1 (patch)
tree8c7c54766537aaf1304fc121d2707e6de80b58b6
parent98a5c64d34237e67d6f6e1b034b6e00c98096524 (diff)
downloadqtlocation-mapboxgl-upstream/jmalanen-number-format.tar.gz
[core] Add number-format expressionupstream/jmalanen-number-format
-rw-r--r--cmake/core.cmake7
-rw-r--r--cmake/mason-dependencies.cmake1
-rw-r--r--include/mbgl/style/expression/expression.hpp3
-rw-r--r--include/mbgl/style/expression/number_format.hpp40
-rw-r--r--include/mbgl/util/platform.hpp3
-rw-r--r--platform/android/core-files.json1
-rwxr-xr-xplatform/android/src/jni.cpp2
-rw-r--r--platform/android/src/text/format_number.cpp81
-rw-r--r--platform/android/src/text/format_number_jni.hpp29
-rw-r--r--platform/darwin/src/string_nsstring.mm23
-rw-r--r--platform/default/src/mbgl/util/format_number.cpp35
-rw-r--r--platform/linux/config.cmake14
-rw-r--r--platform/node/src/node_expression.cpp3
-rw-r--r--platform/qt/qt.cmake1
-rw-r--r--platform/qt/src/format_number.cpp26
-rw-r--r--src/core-files.json2
-rw-r--r--src/mbgl/style/expression/number_format.cpp215
-rw-r--r--src/mbgl/style/expression/parsing_context.cpp2
-rw-r--r--test/fixtures/expression_equality/number-format-currency.a.json9
-rw-r--r--test/fixtures/expression_equality/number-format-currency.b.json9
-rw-r--r--test/fixtures/expression_equality/number-format-default.a.json5
-rw-r--r--test/fixtures/expression_equality/number-format-default.b.json5
-rw-r--r--test/fixtures/expression_equality/number-format-precision.a.json10
-rw-r--r--test/fixtures/expression_equality/number-format-precision.b.json10
-rw-r--r--test/style/expression/expression.test.cpp4
25 files changed, 533 insertions, 7 deletions
diff --git a/cmake/core.cmake b/cmake/core.cmake
index 1971352c37..3278c4f7d3 100644
--- a/cmake/core.cmake
+++ b/cmake/core.cmake
@@ -19,13 +19,18 @@ target_link_libraries(mbgl-core PRIVATE
wagyu
)
+# linux uses ICU from mason, other platforms use vendored ICU
+if(NOT MBGL_PLATFORM STREQUAL "linux")
+ set(ICU_LIBRARY "icu")
+endif()
+
# FIXME: We should not leak these many
# libraries in our public interface.
target_link_libraries(mbgl-core PUBLIC
boost
geojson.hpp
geometry.hpp
- icu
+ ${ICU_LIBRARY}
optional
polylabel
protozero
diff --git a/cmake/mason-dependencies.cmake b/cmake/mason-dependencies.cmake
index a6ef7a3a53..5ec6a44a79 100644
--- a/cmake/mason-dependencies.cmake
+++ b/cmake/mason-dependencies.cmake
@@ -8,6 +8,7 @@ elseif(MBGL_PLATFORM STREQUAL "linux")
mason_use(libuv VERSION 1.9.1)
mason_use(libpng VERSION 1.6.25)
mason_use(libjpeg-turbo VERSION 1.5.0)
+ mason_use(icu VERSION 63.1-min-static-data)
if(WITH_EGL)
mason_use(swiftshader VERSION 2018-05-31)
diff --git a/include/mbgl/style/expression/expression.hpp b/include/mbgl/style/expression/expression.hpp
index 22ae57c2be..5f66fc6dc7 100644
--- a/include/mbgl/style/expression/expression.hpp
+++ b/include/mbgl/style/expression/expression.hpp
@@ -142,7 +142,8 @@ enum class Kind : int32_t {
All,
Comparison,
FormatExpression,
- FormatSectionOverride
+ FormatSectionOverride,
+ NumberFormat
};
class Expression {
diff --git a/include/mbgl/style/expression/number_format.hpp b/include/mbgl/style/expression/number_format.hpp
new file mode 100644
index 0000000000..9571c7d98a
--- /dev/null
+++ b/include/mbgl/style/expression/number_format.hpp
@@ -0,0 +1,40 @@
+#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_,
+ std::unique_ptr<Expression> locale_,
+ std::unique_ptr<Expression> currency_,
+ std::unique_ptr<Expression> minFractionDigits_,
+ std::unique_ptr<Expression> maxFractionDigits_);
+
+ ~NumberFormat();
+
+ 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
diff --git a/include/mbgl/util/platform.hpp b/include/mbgl/util/platform.hpp
index 3544659740..2e11e5f186 100644
--- a/include/mbgl/util/platform.hpp
+++ b/include/mbgl/util/platform.hpp
@@ -16,6 +16,9 @@ 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);
diff --git a/platform/android/core-files.json b/platform/android/core-files.json
index 7e2f7cc07b..62ecc0c671 100644
--- a/platform/android/core-files.json
+++ b/platform/android/core-files.json
@@ -81,6 +81,7 @@
"platform/android/src/style/value.cpp",
"platform/android/src/text/collator.cpp",
"platform/android/src/text/local_glyph_rasterizer.cpp",
+ "platform/android/src/text/format_number.cpp",
"platform/android/src/gl_functions.cpp",
"platform/android/src/thread.cpp",
"platform/android/src/timer.cpp",
diff --git a/platform/android/src/jni.cpp b/platform/android/src/jni.cpp
index 410c962384..088b3b796c 100755
--- a/platform/android/src/jni.cpp
+++ b/platform/android/src/jni.cpp
@@ -54,6 +54,7 @@
#endif
#include "text/collator_jni.hpp"
#include "text/local_glyph_rasterizer_jni.hpp"
+#include "text/format_number_jni.hpp"
#include "logger.hpp"
namespace mbgl {
@@ -200,6 +201,7 @@ void registerNatives(JavaVM *vm) {
Locale::registerNative(env);
Collator::registerNative(env);
StringUtils::registerNative(env);
+ NumberFormat::registerNative(env);
// Logger
Logger::registerNative(env);
diff --git a/platform/android/src/text/format_number.cpp b/platform/android/src/text/format_number.cpp
new file mode 100644
index 0000000000..3a41175ecc
--- /dev/null
+++ b/platform/android/src/text/format_number.cpp
@@ -0,0 +1,81 @@
+#include <mbgl/style/expression/collator.hpp>
+#include <mbgl/text/language_tag.hpp>
+#include <mbgl/util/platform.hpp>
+
+#include <jni/jni.hpp>
+
+#include "../attach_env.hpp"
+#include "format_number_jni.hpp"
+
+namespace mbgl {
+namespace android {
+
+void NumberFormat::registerNative(jni::JNIEnv& env) {
+ jni::Class<NumberFormat>::Singleton(env);
+}
+
+jni::Local<jni::Object<NumberFormat>> NumberFormat::getInstance(jni::JNIEnv& env, const jni::Object<Locale>& locale) {
+ static auto& javaClass = jni::Class<NumberFormat>::Singleton(env);
+ static auto method = javaClass.GetStaticMethod<jni::Object<NumberFormat> (jni::Object<Locale>)>(env, "getInstance");
+ return javaClass.Call(env, method, locale);
+}
+
+jni::Local<jni::Object<NumberFormat>> NumberFormat::getCurrencyInstance(jni::JNIEnv& env, const jni::Object<Locale>& locale) {
+ static auto& javaClass = jni::Class<NumberFormat>::Singleton(env);
+ static auto method = javaClass.GetStaticMethod<jni::Object<NumberFormat> (jni::Object<Locale>)>(env, "getCurrencyInstance");
+ return javaClass.Call(env, method, locale);
+}
+
+jni::Local<jni::String> NumberFormat::format(jni::JNIEnv& env, const jni::Object<NumberFormat>& nf, jni::jdouble number) {
+ static auto& javaClass = jni::Class<NumberFormat>::Singleton(env);
+ static auto method = javaClass.GetMethod<jni::String (jni::jdouble)>(env, "format");
+ return nf.Call(env, method, number);
+}
+
+void NumberFormat::setMinimumFractionDigits(jni::JNIEnv& env, const jni::Object<NumberFormat>& nf, jni::jint value) {
+ static auto& javaClass = jni::Class<NumberFormat>::Singleton(env);
+ static auto method = javaClass.GetMethod<void (jni::jint)>(env, "setMinimumFractionDigits");
+ return nf.Call(env, method, value);
+}
+
+void NumberFormat::setMaximumFractionDigits(jni::JNIEnv& env, const jni::Object<NumberFormat>& nf, jni::jint value) {
+ static auto& javaClass = jni::Class<NumberFormat>::Singleton(env);
+ static auto method = javaClass.GetMethod<void (jni::jint)>(env, "setMaximumFractionDigits");
+ return nf.Call(env, method, value);
+}
+
+} // namespace android
+
+namespace platform {
+
+std::string formatNumber(double number, const std::string& localeId, const std::string& currency,
+ uint8_t minFractionDigits, uint8_t maxFractionDigits) {
+
+ auto env{ android::AttachEnv() };
+
+ jni::Global<jni::Object<android::Locale>> locale;
+ LanguageTag languageTag = !localeId.empty() ? LanguageTag::fromBCP47(localeId) : LanguageTag();
+ if (!languageTag.language) {
+ locale = jni::NewGlobal(*env, android::Locale::getDefault(*env));
+ } else if (!languageTag.region) {
+ locale = jni::NewGlobal(*env, android::Locale::New(*env, jni::Make<jni::String>(*env, *languageTag.language)));
+ } else {
+ locale = jni::NewGlobal(*env, android::Locale::New(*env, jni::Make<jni::String>(*env, *languageTag.language),
+ jni::Make<jni::String>(*env, *languageTag.region)));
+ }
+
+ jni::Global<jni::Object<android::NumberFormat>> formatter;
+ if (currency.empty()) {
+ formatter = jni::NewGlobal(*env, android::NumberFormat::getInstance(*env, locale));
+ android::NumberFormat::setMinimumFractionDigits(*env, formatter, static_cast<jni::jint>(minFractionDigits));
+ android::NumberFormat::setMaximumFractionDigits(*env, formatter, static_cast<jni::jint>(maxFractionDigits));
+ } else {
+ formatter = jni::NewGlobal(*env, android::NumberFormat::getCurrencyInstance(*env, locale));
+ }
+
+ auto result = android::NumberFormat::format(*env, formatter, static_cast<jni::jdouble>(number));
+ return jni::Make<std::string>(*env, result);
+}
+
+} // namespace platform
+} // namespace mbgl
diff --git a/platform/android/src/text/format_number_jni.hpp b/platform/android/src/text/format_number_jni.hpp
new file mode 100644
index 0000000000..1720038925
--- /dev/null
+++ b/platform/android/src/text/format_number_jni.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <jni/jni.hpp>
+
+#include "collator_jni.hpp"
+
+/*
+ android::NumberFormat is the JNI wrapper
+ of java/text/NumberFormat.
+ */
+
+namespace mbgl {
+namespace android {
+
+class NumberFormat {
+public:
+ static constexpr auto Name() { return "java/text/NumberFormat"; };
+
+ static jni::Local<jni::Object<NumberFormat>> getInstance(jni::JNIEnv&, const jni::Object<Locale>&);
+ static jni::Local<jni::Object<NumberFormat>> getCurrencyInstance(jni::JNIEnv&, const jni::Object<Locale>&);
+ static jni::Local<jni::String> format(jni::JNIEnv&, const jni::Object<NumberFormat>&, jni::jdouble);
+ static void setMinimumFractionDigits(jni::JNIEnv&, const jni::Object<NumberFormat>&, jni::jint);
+ static void setMaximumFractionDigits(jni::JNIEnv&, const jni::Object<NumberFormat>&, jni::jint);
+
+ static void registerNative(jni::JNIEnv&);
+};
+
+} // namespace android
+} // namespace mbgl
diff --git a/platform/darwin/src/string_nsstring.mm b/platform/darwin/src/string_nsstring.mm
index 08f9aeccef..096ed2b212 100644
--- a/platform/darwin/src/string_nsstring.mm
+++ b/platform/darwin/src/string_nsstring.mm
@@ -27,5 +27,28 @@ std::string lowercase(const std::string &string) {
return result;
}
+std::string formatNumber(double number, const std::string& localeId, const std::string& currency,
+ uint8_t minFractionDigits, uint8_t maxFractionDigits) {
+
+ static NSNumberFormatter *numberFormatter;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ 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 = NSNumberFormatterDecimalStyle;
+ } else {
+ numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle;
+ }
+ NSString *formatted = [numberFormatter stringFromNumber:@(number)];
+ std::string result = std::string([formatted UTF8String]);
+ return result;
+}
+
}
}
diff --git a/platform/default/src/mbgl/util/format_number.cpp b/platform/default/src/mbgl/util/format_number.cpp
new file mode 100644
index 0000000000..7cc863818a
--- /dev/null
+++ b/platform/default/src/mbgl/util/format_number.cpp
@@ -0,0 +1,35 @@
+#include <mbgl/util/platform.hpp>
+
+#include <unicode/numberformatter.h>
+
+namespace mbgl {
+namespace platform {
+
+std::string formatNumber(double number, const std::string& localeId, const std::string& currency,
+ uint8_t minFractionDigits, uint8_t maxFractionDigits) {
+
+ UErrorCode status = U_ZERO_ERROR;
+ icu::UnicodeString ustr;
+ std::string formatted;
+
+ icu::Locale locale = icu::Locale(localeId.c_str());
+ // Print the value as currency
+ if (!currency.empty()) {
+ icu::UnicodeString ucurrency = icu::UnicodeString::fromUTF8(currency);
+ ustr = icu::number::NumberFormatter::with()
+ .unit(icu::CurrencyUnit(ucurrency.getBuffer(), status))
+ .locale(locale)
+ .formatDouble(number, status)
+ .toString();
+ } else {
+ ustr = icu::number::NumberFormatter::with()
+ .precision(icu::number::Precision::minMaxFraction(minFractionDigits, maxFractionDigits))
+ .locale(locale)
+ .formatDouble(number, status)
+ .toString();
+ }
+ return ustr.toUTF8String(formatted);
+}
+
+} // namespace platform
+} // namespace mbgl
diff --git a/platform/linux/config.cmake b/platform/linux/config.cmake
index 7cc1f1fe4d..aa65ddb606 100644
--- a/platform/linux/config.cmake
+++ b/platform/linux/config.cmake
@@ -56,6 +56,7 @@ macro(mbgl_platform_core)
PRIVATE platform/default/src/mbgl/text/unaccent.cpp
PRIVATE platform/default/include/mbgl/text/unaccent.hpp
PRIVATE platform/default/src/mbgl/util/utf.cpp
+ PRIVATE platform/default/src/mbgl/util/format_number.cpp
# Image handling
PRIVATE platform/default/src/mbgl/util/image.cpp
@@ -83,10 +84,21 @@ macro(mbgl_platform_core)
target_add_mason_package(mbgl-core PUBLIC libpng)
target_add_mason_package(mbgl-core PUBLIC libjpeg-turbo)
+ target_add_mason_package(mbgl-core PRIVATE icu)
+
+ # Ignore warning caused by ICU header unistr.h in some CI environments
+ set_source_files_properties(platform/default/src/mbgl/util/format_number.cpp PROPERTIES COMPILE_FLAGS -Wno-error=shadow)
+
+ # Link all ICU libraries (by default only libicuuc is linked)
+ find_library(LIBICUI18N NAMES icui18n HINTS ${MASON_PACKAGE_icu_INCLUDE_DIRS}/../lib)
+ find_library(LIBICUUC NAMES icuuc HINTS ${MASON_PACKAGE_icu_INCLUDE_DIRS}/../lib)
+ find_library(LIBICUDATA NAMES icudata HINTS ${MASON_PACKAGE_icu_INCLUDE_DIRS}/../lib)
target_link_libraries(mbgl-core
+ PRIVATE ${LIBICUI18N}
+ PRIVATE ${LIBICUUC}
+ PRIVATE ${LIBICUDATA}
PRIVATE nunicode
- PRIVATE icu
PUBLIC -lz
)
diff --git a/platform/node/src/node_expression.cpp b/platform/node/src/node_expression.cpp
index 4ea66124f8..041f60d029 100644
--- a/platform/node/src/node_expression.cpp
+++ b/platform/node/src/node_expression.cpp
@@ -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();
diff --git a/platform/qt/qt.cmake b/platform/qt/qt.cmake
index 48523057c9..33acb7a030 100644
--- a/platform/qt/qt.cmake
+++ b/platform/qt/qt.cmake
@@ -35,6 +35,7 @@ set(MBGL_QT_CORE_FILES
PRIVATE platform/qt/src/timer_impl.hpp
PRIVATE platform/qt/src/utf.cpp
PRIVATE platform/qt/src/gl_functions.cpp
+ PRIVATE platform/qt/src/format_number.cpp
PRIVATE platform/default/src/mbgl/text/collator.cpp
PRIVATE platform/default/src/mbgl/text/unaccent.cpp
diff --git a/platform/qt/src/format_number.cpp b/platform/qt/src/format_number.cpp
new file mode 100644
index 0000000000..b6fe3558e6
--- /dev/null
+++ b/platform/qt/src/format_number.cpp
@@ -0,0 +1,26 @@
+#include <mbgl/util/platform.hpp>
+
+#include <QLocale>
+#include <QString>
+
+namespace mbgl {
+namespace platform {
+
+std::string formatNumber(double number, const std::string& localeId, const std::string& currency,
+ uint8_t minFractionDigits, uint8_t maxFractionDigits) {
+
+ QString formatted;
+ // Qt Locale::toString() API takes only one precision argument
+ (void)minFractionDigits;
+ QLocale locale = QLocale(QString::fromStdString(localeId));
+
+ if (!currency.empty()) {
+ formatted = locale.toCurrencyString(number);
+ } else {
+ formatted = locale.toString(number, 'f', maxFractionDigits);
+ }
+ return formatted.toStdString();
+}
+
+} // namespace platform
+} // namespace mbgl
diff --git a/src/core-files.json b/src/core-files.json
index 665c203a78..fcc0dba9f5 100644
--- a/src/core-files.json
+++ b/src/core-files.json
@@ -192,6 +192,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",
@@ -416,6 +417,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",
diff --git a/src/mbgl/style/expression/number_format.cpp b/src/mbgl/style/expression/number_format.cpp
new file mode 100644
index 0000000000..e31a9ce398
--- /dev/null
+++ b/src/mbgl/style/expression/number_format.cpp
@@ -0,0 +1,215 @@
+#include <mbgl/style/expression/number_format.hpp>
+#include <mbgl/style/conversion_impl.hpp>
+#include <mbgl/util/platform.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+const char localeKey[] = "locale";
+const char currencyKey[] = "currency";
+const char minFractionDigitsKey[] = "min-fraction-digits";
+const char maxFractionDigitsKey[] = "max-fraction-digits";
+
+NumberFormat::NumberFormat(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_)
+ : Expression(Kind::NumberFormat, type::String),
+ number(std::move(number_)),
+ locale(std::move(locale_)),
+ currency(std::move(currency_)),
+ minFractionDigits(std::move(minFractionDigits_)),
+ maxFractionDigits(std::move(maxFractionDigits_))
+{}
+
+NumberFormat::~NumberFormat() = default;
+
+EvaluationResult NumberFormat::evaluate(const EvaluationContext& params) const {
+ auto numberResult = number->evaluate(params);
+ if (!numberResult) {
+ return numberResult.error();
+ }
+ double evaluatedNumber = numberResult->get<double>();
+
+ std::string evaluatedLocale;
+ if (locale) {
+ auto localeResult = locale->evaluate(params);
+ if (!localeResult) {
+ return localeResult.error();
+ }
+ evaluatedLocale = toString(*localeResult);
+ }
+
+ std::string evaluatedCurrency;
+ if (currency) {
+ auto currencyResult = currency->evaluate(params);
+ if (!currencyResult) {
+ return currencyResult.error();
+ }
+ evaluatedCurrency = toString(*currencyResult);
+ }
+
+ uint8_t evaluatedMinFractionDigits = 0;
+ if (minFractionDigits) {
+ auto minDigitsResult = minFractionDigits->evaluate(params);
+ if (!minDigitsResult) {
+ return minDigitsResult.error();
+ }
+ evaluatedMinFractionDigits = minDigitsResult->get<double>();
+ }
+
+ uint8_t evaluatedMaxFractionDigits = 3;
+ if (maxFractionDigits) {
+ auto maxDigitsResult = maxFractionDigits->evaluate(params);
+ if (!maxDigitsResult) {
+ return maxDigitsResult.error();
+ }
+ evaluatedMaxFractionDigits = maxDigitsResult->get<double>();
+ }
+
+ std::string output;
+ output = platform::formatNumber(evaluatedNumber,
+ evaluatedLocale,
+ evaluatedCurrency,
+ evaluatedMinFractionDigits,
+ evaluatedMaxFractionDigits);
+ return output;
+}
+
+void NumberFormat::eachChild(const std::function<void(const Expression&)>& visit) const {
+ visit(*number);
+ if (locale) visit(*locale);
+ if (currency) visit(*currency);
+ if (minFractionDigits) visit(*minFractionDigits);
+ if (maxFractionDigits) visit(*maxFractionDigits);
+}
+
+bool NumberFormat::operator==(const Expression& e) const {
+ if (e.getKind() == Kind::NumberFormat) {
+ auto rhs = static_cast<const NumberFormat*>(&e);
+ if ((locale && (!rhs->locale || *locale != *rhs->locale)) ||
+ (!locale && rhs->locale)) {
+ return false;
+ }
+ if ((currency && (!rhs->currency || *currency != *rhs->currency)) ||
+ (!currency && rhs->currency)) {
+ return false;
+ }
+ if ((minFractionDigits && (!rhs->minFractionDigits || *minFractionDigits != *rhs->minFractionDigits)) ||
+ (!minFractionDigits && rhs->minFractionDigits)) {
+ return false;
+ }
+ if ((maxFractionDigits && (!rhs->maxFractionDigits || *maxFractionDigits != *rhs->maxFractionDigits)) ||
+ (!maxFractionDigits && rhs->maxFractionDigits)) {
+ return false;
+ }
+ return *number == *rhs->number;
+ }
+ return false;
+}
+
+std::vector<optional<Value>> NumberFormat::possibleOutputs() const {
+ return { nullopt };
+}
+
+using namespace mbgl::style::conversion;
+ParseResult NumberFormat::parse(const Convertible& value, ParsingContext& ctx) {
+ std::size_t length = arrayLength(value);
+
+ if (length != 3) {
+ ctx.error("Expected two arguments, but found " + util::toString(length) + " instead.");
+ return ParseResult();
+ }
+
+ ParseResult numberResult = ctx.parse(arrayMember(value, 1), 1, {type::Number});
+ if (!numberResult) {
+ ctx.error("Failed to parse the number.");
+ return ParseResult();
+ }
+
+ type::Type type = (*numberResult)->getType();
+ if (!type.is<type::NumberType>()) {
+ ctx.error("Expected argument of type number, but found " + toString(type) + " instead.");
+ return ParseResult();
+ }
+
+ auto options = arrayMember(value, 2);
+ if (!isObject(options)) {
+ ctx.error("Number-format options argument must be an object.");
+ return ParseResult();
+ }
+
+ const optional<Convertible> localeOption = objectMember(options, localeKey);
+ ParseResult localeResult;
+ if (localeOption) {
+ localeResult = ctx.parse(*localeOption, 1, {type::String});
+ if (!localeResult) {
+ ctx.error("Number-format locale parsing failed.");
+ return ParseResult();
+ }
+ }
+
+ const optional<Convertible> currencyOption = objectMember(options, currencyKey);
+ ParseResult currencyResult;
+ if (currencyOption) {
+ currencyResult = ctx.parse(*currencyOption, 1, {type::String});
+ if (!currencyResult) {
+ ctx.error("Number-format currency parsing failed.");
+ return ParseResult();
+ }
+ }
+
+ const optional<Convertible> minFractionDigitsOption = objectMember(options, minFractionDigitsKey);
+ ParseResult minFractionDigitsResult;
+ if (minFractionDigitsOption) {
+ minFractionDigitsResult = ctx.parse(*minFractionDigitsOption, 1, {type::Number});
+ if (!minFractionDigitsResult) {
+ ctx.error("Number-format min-fraction-digits parsing failed.");
+ return ParseResult();
+ }
+ }
+
+ const optional<Convertible> maxFractionDigitsOption = objectMember(options, maxFractionDigitsKey);
+ ParseResult maxFractionDigitsResult;
+ if (maxFractionDigitsOption) {
+ maxFractionDigitsResult = ctx.parse(*maxFractionDigitsOption, 1, {type::Number});
+ if (!maxFractionDigitsResult) {
+ ctx.error("Number-format max-fraction-digits parsing failed.");
+ return ParseResult();
+ }
+ }
+
+ return ParseResult(std::make_unique<NumberFormat>(std::move(*numberResult),
+ localeResult ? std::move(*localeResult) : nullptr,
+ currencyResult? std::move(*currencyResult) : nullptr,
+ minFractionDigitsResult ? std::move(*minFractionDigitsResult) : nullptr,
+ maxFractionDigitsResult ? std::move(*maxFractionDigitsResult) : nullptr));
+}
+
+mbgl::Value NumberFormat::serialize() const {
+ std::vector<mbgl::Value> serialized{{ getOperator() }};
+ serialized.emplace_back(number->serialize());
+
+ std::unordered_map<std::string, mbgl::Value> options;
+ if (locale) {
+ options[localeKey] = locale->serialize();
+ }
+ if (currency) {
+ options[currencyKey] = currency->serialize();
+ }
+ if (minFractionDigits) {
+ options[minFractionDigitsKey] = minFractionDigits->serialize();
+ }
+ if (maxFractionDigits) {
+ options[maxFractionDigitsKey] = maxFractionDigits->serialize();
+ }
+ serialized.emplace_back(options);
+
+ return serialized;
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/parsing_context.cpp b/src/mbgl/style/expression/parsing_context.cpp
index 411aa660e8..a7c04f563d 100644
--- a/src/mbgl/style/expression/parsing_context.cpp
+++ b/src/mbgl/style/expression/parsing_context.cpp
@@ -13,6 +13,7 @@
#include <mbgl/style/expression/coercion.hpp>
#include <mbgl/style/expression/compound_expression.hpp>
#include <mbgl/style/expression/comparison.hpp>
+#include <mbgl/style/expression/number_format.hpp>
#include <mbgl/style/expression/format_expression.hpp>
#include <mbgl/style/expression/interpolate.hpp>
#include <mbgl/style/expression/length.hpp>
@@ -120,6 +121,7 @@ MAPBOX_ETERNAL_CONSTEXPR const auto expressionRegistry = mapbox::eternal::hash_m
{"literal", Literal::parse},
{"match", parseMatch},
{"number", Assertion::parse},
+ {"number-format", NumberFormat::parse},
{"object", Assertion::parse},
{"step", Step::parse},
{"string", Assertion::parse},
diff --git a/test/fixtures/expression_equality/number-format-currency.a.json b/test/fixtures/expression_equality/number-format-currency.a.json
new file mode 100644
index 0000000000..d22fb5bc4a
--- /dev/null
+++ b/test/fixtures/expression_equality/number-format-currency.a.json
@@ -0,0 +1,9 @@
+[
+ "number-format",
+ 123456.789,
+ {
+ "locale": "de-DE",
+ "currency": "EUR"
+ }
+]
+
diff --git a/test/fixtures/expression_equality/number-format-currency.b.json b/test/fixtures/expression_equality/number-format-currency.b.json
new file mode 100644
index 0000000000..c85b457142
--- /dev/null
+++ b/test/fixtures/expression_equality/number-format-currency.b.json
@@ -0,0 +1,9 @@
+[
+ "number-format",
+ 123456.789,
+ {
+ "locale": "ja-JP",
+ "currency": "JPY"
+ }
+]
+
diff --git a/test/fixtures/expression_equality/number-format-default.a.json b/test/fixtures/expression_equality/number-format-default.a.json
new file mode 100644
index 0000000000..f41348d8f8
--- /dev/null
+++ b/test/fixtures/expression_equality/number-format-default.a.json
@@ -0,0 +1,5 @@
+[
+ "number-format",
+ 123456.789, {}
+]
+
diff --git a/test/fixtures/expression_equality/number-format-default.b.json b/test/fixtures/expression_equality/number-format-default.b.json
new file mode 100644
index 0000000000..8bbc1bcfb5
--- /dev/null
+++ b/test/fixtures/expression_equality/number-format-default.b.json
@@ -0,0 +1,5 @@
+[
+ "number-format",
+ -123456.789, {}
+]
+
diff --git a/test/fixtures/expression_equality/number-format-precision.a.json b/test/fixtures/expression_equality/number-format-precision.a.json
new file mode 100644
index 0000000000..24d56d8d10
--- /dev/null
+++ b/test/fixtures/expression_equality/number-format-precision.a.json
@@ -0,0 +1,10 @@
+[
+ "number-format",
+ 987654321.23456789,
+ {
+ "locale": "en-US",
+ "min-fraction-digits": 15,
+ "max-fraction-digits": 20
+ }
+]
+
diff --git a/test/fixtures/expression_equality/number-format-precision.b.json b/test/fixtures/expression_equality/number-format-precision.b.json
new file mode 100644
index 0000000000..d8935f9f5f
--- /dev/null
+++ b/test/fixtures/expression_equality/number-format-precision.b.json
@@ -0,0 +1,10 @@
+[
+ "number-format",
+ 987654321.23456789,
+ {
+ "locale": "en-US",
+ "min-fraction-digits": 2,
+ "max-fraction-digits": 4
+ }
+]
+
diff --git a/test/style/expression/expression.test.cpp b/test/style/expression/expression.test.cpp
index 42a8fdd726..8e2acfcd32 100644
--- a/test/style/expression/expression.test.cpp
+++ b/test/style/expression/expression.test.cpp
@@ -36,10 +36,8 @@ TEST(Expression, IsExpression) {
// TODO: "feature-state": https://github.com/mapbox/mapbox-gl-native/issues/12613
// TODO: "interpolate-hcl": https://github.com/mapbox/mapbox-gl-native/issues/8720
// TODO: "interpolate-lab": https://github.com/mapbox/mapbox-gl-native/issues/8720
- // TODO: "number-format": https://github.com/mapbox/mapbox-gl-native/issues/13632
// TODO: "accumulated": https://github.com/mapbox/mapbox-gl-native/issues/14043
- if (name == "feature-state" || name == "interpolate-hcl" || name == "interpolate-lab" || name == "number-format" ||
- name == "accumulated") {
+ if (name == "feature-state" || name == "interpolate-hcl" || name == "interpolate-lab" || name == "accumulated") {
if (expression::isExpression(conversion::Convertible(expression))) {
ASSERT_TRUE(false) << "Expression name" << name << "is implemented - please update Expression.IsExpression test.";
}