* Copyright (c) 2021-2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ecmascript/js_number_format.h"
#include "ecmascript/js_function.h"
#include "ecmascript/object_factory-inl.h"
namespace panda::ecmascript {
const std::vector<StyleOption> JSNumberFormat::STYLE_OPTION = {
StyleOption::DECIMAL, StyleOption::PERCENT, StyleOption::CURRENCY, StyleOption::UNIT
};
const std::vector<std::string> JSNumberFormat::STYLE_OPTION_NAME = {
"decimal", "percent", "currency", "unit"
};
const std::vector<CurrencyDisplayOption> JSNumberFormat::CURRENCY_DISPLAY_OPTION = {
CurrencyDisplayOption::CODE, CurrencyDisplayOption::SYMBOL,
CurrencyDisplayOption::NARROWSYMBOL, CurrencyDisplayOption::NAME
};
const std::vector<std::string> JSNumberFormat::CURRENCY_DISPLAY_OPTION_NAME = {
"code", "symbol", "narrowSymbol", "name"
};
const std::vector<CurrencySignOption> JSNumberFormat::CURRENCY_SIGN_OPTION = {
CurrencySignOption::STANDARD, CurrencySignOption::ACCOUNTING
};
const std::vector<std::string> JSNumberFormat::CURRENCY_SIGN_OPTION_NAME = {"standard", "accounting"};
const std::vector<UnitDisplayOption> JSNumberFormat::UNIT_DISPLAY_OPTION = {
UnitDisplayOption::SHORT, UnitDisplayOption::NARROW, UnitDisplayOption::LONG
};
const std::vector<std::string> JSNumberFormat::UNIT_DISPLAY_OPTION_NAME = {"short", "narrow", "long"};
const std::vector<LocaleMatcherOption> JSNumberFormat::LOCALE_MATCHER_OPTION = {
LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT
};
const std::vector<std::string> JSNumberFormat::LOCALE_MATCHER_OPTION_NAME = {"lookup", "best fit"};
const std::vector<NotationOption> JSNumberFormat::NOTATION_OPTION = {
NotationOption::STANDARD, NotationOption::SCIENTIFIC,
NotationOption::ENGINEERING, NotationOption::COMPACT
};
const std::vector<std::string> JSNumberFormat::NOTATION_OPTION_NAME = {
"standard", "scientific", "engineering", "compact"
};
const std::vector<SignDisplayOption> JSNumberFormat::SIGN_DISPLAY_OPTION = {
SignDisplayOption::AUTO, SignDisplayOption::NEVER,
SignDisplayOption::ALWAYS, SignDisplayOption::EXCEPTZERO
};
const std::vector<std::string> JSNumberFormat::SIGN_DISPLAY_OPTION_NAME = {
"auto", "never", "always", "exceptZero"
};
const std::vector<CompactDisplayOption> JSNumberFormat::COMPACT_DISPLAY_OPTION = {
CompactDisplayOption::SHORT, CompactDisplayOption::LONG
};
const std::vector<std::string> JSNumberFormat::COMPACT_DISPLAY_OPTION_NAME = {"short", "long"};
const std::set<std::string> SANCTIONED_UNIT({ "acre", "bit", "byte", "celsius", "centimeter", "day", "degree",
"fahrenheit", "fluid-ounce", "foot", "gallon", "gigabit", "gigabyte",
"gram", "hectare", "hour", "inch", "kilobit", "kilobyte", "kilogram",
"kilometer", "liter", "megabit", "megabyte", "meter", "microsecond", "mile",
"mile-scandinavian", "millimeter", "milliliter", "millisecond",
"minute", "month", "nanosecond", "ounce", "percent", "petabyte", "pound",
"second", "stone", "terabit", "terabyte", "week", "yard", "year" });
JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, StyleOption style)
{
JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
auto globalConst = thread->GlobalConstants();
switch (style) {
case StyleOption::DECIMAL:
result.Update(globalConst->GetHandledDecimalString().GetTaggedValue());
break;
case StyleOption::CURRENCY:
result.Update(globalConst->GetHandledCurrencyString().GetTaggedValue());
break;
case StyleOption::PERCENT:
result.Update(globalConst->GetHandledPercentString().GetTaggedValue());
break;
case StyleOption::UNIT:
result.Update(globalConst->GetHandledUnitString().GetTaggedValue());
break;
default:
LOG_ECMA(FATAL) << "this branch is unreachable";
UNREACHABLE();
}
return result;
}
JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, CurrencyDisplayOption currencyDisplay)
{
JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
auto globalConst = thread->GlobalConstants();
switch (currencyDisplay) {
case CurrencyDisplayOption::CODE:
result.Update(globalConst->GetHandledCodeString().GetTaggedValue());
break;
case CurrencyDisplayOption::SYMBOL:
result.Update(globalConst->GetHandledSymbolString().GetTaggedValue());
break;
case CurrencyDisplayOption::NARROWSYMBOL:
result.Update(globalConst->GetHandledNarrowSymbolString().GetTaggedValue());
break;
case CurrencyDisplayOption::NAME:
result.Update(globalConst->GetHandledNameString().GetTaggedValue());
break;
default:
LOG_ECMA(FATAL) << "this branch is unreachable";
UNREACHABLE();
}
return result;
}
JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, CurrencySignOption currencySign)
{
auto globalConst = thread->GlobalConstants();
JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
switch (currencySign) {
case CurrencySignOption::STANDARD:
result.Update(globalConst->GetHandledStandardString().GetTaggedValue());
break;
case CurrencySignOption::ACCOUNTING:
result.Update(globalConst->GetHandledAccountingString().GetTaggedValue());
break;
default:
LOG_ECMA(FATAL) << "this branch is unreachable";
UNREACHABLE();
}
return result;
}
JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, UnitDisplayOption unitDisplay)
{
JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
auto globalConst = thread->GlobalConstants();
switch (unitDisplay) {
case UnitDisplayOption::SHORT:
result.Update(globalConst->GetHandledShortString().GetTaggedValue());
break;
case UnitDisplayOption::NARROW:
result.Update(globalConst->GetHandledNarrowString().GetTaggedValue());
break;
case UnitDisplayOption::LONG:
result.Update(globalConst->GetHandledLongString().GetTaggedValue());
break;
default:
LOG_ECMA(FATAL) << "this branch is unreachable";
UNREACHABLE();
}
return result;
}
JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, NotationOption notation)
{
JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
auto globalConst = thread->GlobalConstants();
switch (notation) {
case NotationOption::STANDARD:
result.Update(globalConst->GetHandledStandardString().GetTaggedValue());
break;
case NotationOption::SCIENTIFIC:
result.Update(globalConst->GetHandledScientificString().GetTaggedValue());
break;
case NotationOption::ENGINEERING:
result.Update(globalConst->GetHandledEngineeringString().GetTaggedValue());
break;
case NotationOption::COMPACT:
result.Update(globalConst->GetHandledCompactString().GetTaggedValue());
break;
default:
LOG_ECMA(FATAL) << "this branch is unreachable";
UNREACHABLE();
}
return result;
}
JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, CompactDisplayOption compactDisplay)
{
JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
auto globalConst = thread->GlobalConstants();
switch (compactDisplay) {
case CompactDisplayOption::SHORT:
result.Update(globalConst->GetHandledShortString().GetTaggedValue());
break;
case CompactDisplayOption::LONG:
result.Update(globalConst->GetHandledLongString().GetTaggedValue());
break;
default:
LOG_ECMA(FATAL) << "this branch is unreachable";
UNREACHABLE();
}
return result;
}
JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, SignDisplayOption signDisplay)
{
JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
auto globalConst = thread->GlobalConstants();
switch (signDisplay) {
case SignDisplayOption::AUTO:
result.Update(globalConst->GetHandledAutoString().GetTaggedValue());
break;
case SignDisplayOption::ALWAYS:
result.Update(globalConst->GetHandledAlwaysString().GetTaggedValue());
break;
case SignDisplayOption::NEVER:
result.Update(globalConst->GetHandledNeverString().GetTaggedValue());
break;
case SignDisplayOption::EXCEPTZERO:
result.Update(globalConst->GetHandledExceptZeroString().GetTaggedValue());
break;
default:
LOG_ECMA(FATAL) << "this branch is unreachable";
UNREACHABLE();
}
return result;
}
icu::MeasureUnit ToMeasureUnit(const std::string &sanctionedUnit)
{
UErrorCode status = U_ZERO_ERROR;
int32_t total = icu::MeasureUnit::getAvailable(nullptr, 0, status);
status = U_ZERO_ERROR;
std::vector<icu::MeasureUnit> units(total);
icu::MeasureUnit::getAvailable(units.data(), total, status);
ASSERT(U_SUCCESS(status));
for (auto &unit : units) {
if (std::strcmp(sanctionedUnit.c_str(), unit.getSubtype()) == 0) {
return unit;
}
}
return icu::MeasureUnit();
}
bool IsSanctionedSimpleUnitIdentifier(const std::string &unit)
{
auto it = SANCTIONED_UNIT.find(unit);
if (it != SANCTIONED_UNIT.end()) {
return true;
}
return false;
}
bool IsWellFormedUnitIdentifier(const std::string &unit, icu::MeasureUnit &icuUnit, icu::MeasureUnit &icuPerUnit)
{
icu::MeasureUnit result = icu::MeasureUnit();
icu::MeasureUnit emptyUnit = icu::MeasureUnit();
auto pos = unit.find("-per-");
if (IsSanctionedSimpleUnitIdentifier(unit) && pos == std::string::npos) {
result = ToMeasureUnit(unit);
icuUnit = result;
icuPerUnit = emptyUnit;
return true;
}
size_t afterPos = pos + JSNumberFormat::PERUNIT_STRING;
if (pos == std::string::npos || unit.find("-per-", afterPos) != std::string::npos) {
return false;
}
std::string numerator = unit.substr(0, pos);
if (IsSanctionedSimpleUnitIdentifier(numerator)) {
result = ToMeasureUnit(numerator);
} else {
return false;
}
std::string denominator = unit.substr(pos + JSNumberFormat::PERUNIT_STRING);
icu::MeasureUnit perResult = icu::MeasureUnit();
if (IsSanctionedSimpleUnitIdentifier(denominator)) {
perResult = ToMeasureUnit(denominator);
} else {
return false;
}
icuUnit = result;
icuPerUnit = perResult;
return true;
}
FractionDigitsOption SetNumberFormatUnitOptions(JSThread *thread,
const JSHandle<JSNumberFormat> &numberFormat,
const JSHandle<JSObject> &optionsObject,
icu::number::LocalizedNumberFormatter *icuNumberFormatter)
{
auto globalConst = thread->GlobalConstants();
FractionDigitsOption fractionDigitsOption;
JSHandle<JSTaggedValue> property = globalConst->GetHandledStyleString();
auto style = JSLocale::GetOptionOfString<StyleOption>(
thread, optionsObject, property,
JSNumberFormat::STYLE_OPTION, JSNumberFormat::STYLE_OPTION_NAME,
StyleOption::DECIMAL);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
numberFormat->SetStyle(style);
property = globalConst->GetHandledCurrencyString();
JSHandle<JSTaggedValue> undefinedValue(thread, JSTaggedValue::Undefined());
JSHandle<JSTaggedValue> currency =
JSLocale::GetOption(thread, optionsObject, property, OptionType::STRING, undefinedValue, undefinedValue);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
if (!currency->IsUndefined()) {
JSHandle<EcmaString> currencyStr = JSHandle<EcmaString>::Cast(currency);
if (EcmaStringAccessor(currencyStr).IsUtf16()) {
THROW_RANGE_ERROR_AND_RETURN(thread, "not a utf-8", fractionDigitsOption);
}
std::string currencyCStr = intl::LocaleHelper::ConvertToStdString(thread, currencyStr);
if (!JSLocale::IsWellFormedCurrencyCode(currencyCStr)) {
THROW_RANGE_ERROR_AND_RETURN(thread, "not a wellformed code", fractionDigitsOption);
}
} else {
if (style == StyleOption::CURRENCY) {
THROW_TYPE_ERROR_AND_RETURN(thread, "style is currency but currency is undefined", fractionDigitsOption);
}
}
property = globalConst->GetHandledCurrencyDisplayString();
auto currencyDisplay = JSLocale::GetOptionOfString<CurrencyDisplayOption>(
thread, optionsObject, property,
JSNumberFormat::CURRENCY_DISPLAY_OPTION, JSNumberFormat::CURRENCY_DISPLAY_OPTION_NAME,
CurrencyDisplayOption::SYMBOL);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
numberFormat->SetCurrencyDisplay(currencyDisplay);
property = globalConst->GetHandledCurrencySignString();
auto currencySign = JSLocale::GetOptionOfString<CurrencySignOption>(
thread, optionsObject, property,
JSNumberFormat::CURRENCY_SIGN_OPTION, JSNumberFormat::CURRENCY_SIGN_OPTION_NAME,
CurrencySignOption::STANDARD);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
numberFormat->SetCurrencySign(currencySign);
property = globalConst->GetHandledUnitString();
JSHandle<JSTaggedValue> unit =
JSLocale::GetOption(thread, optionsObject, property, OptionType::STRING, undefinedValue, undefinedValue);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
numberFormat->SetUnit(thread, unit);
icu::MeasureUnit icuUnit;
icu::MeasureUnit icuPerUnit;
if (!unit->IsUndefined()) {
JSHandle<EcmaString> unitStr = JSHandle<EcmaString>::Cast(unit);
if (EcmaStringAccessor(unitStr).IsUtf16()) {
THROW_RANGE_ERROR_AND_RETURN(thread, "Unit input is illegal", fractionDigitsOption);
}
std::string str = intl::LocaleHelper::ConvertToStdString(thread, unitStr);
if (!IsWellFormedUnitIdentifier(str, icuUnit, icuPerUnit)) {
THROW_RANGE_ERROR_AND_RETURN(thread, "Unit input is illegal", fractionDigitsOption);
}
} else {
if (style == StyleOption::UNIT) {
THROW_TYPE_ERROR_AND_RETURN(thread, "style is unit but unit is undefined", fractionDigitsOption);
}
}
property = globalConst->GetHandledUnitDisplayString();
auto unitDisplay = JSLocale::GetOptionOfString<UnitDisplayOption>(
thread, optionsObject, property,
JSNumberFormat::UNIT_DISPLAY_OPTION, JSNumberFormat::UNIT_DISPLAY_OPTION_NAME,
UnitDisplayOption::SHORT);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
numberFormat->SetUnitDisplay(unitDisplay);
icu::UnicodeString currencyUStr;
UErrorCode status = U_ZERO_ERROR;
if (style == StyleOption::CURRENCY) {
JSHandle<EcmaString> currencyStr = JSHandle<EcmaString>::Cast(currency);
std::string currencyCStr = intl::LocaleHelper::ConvertToStdString(thread, currencyStr);
std::transform(currencyCStr.begin(), currencyCStr.end(), currencyCStr.begin(), toupper);
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<JSTaggedValue> currencyValue = JSHandle<JSTaggedValue>::Cast(factory->NewFromStdString(currencyCStr));
numberFormat->SetCurrency(thread, currencyValue);
currencyUStr = currencyCStr.c_str();
if (!currencyUStr.isEmpty()) {
*icuNumberFormatter = icuNumberFormatter->unit(icu::CurrencyUnit(currencyUStr.getBuffer(), status));
ASSERT(U_SUCCESS(status));
UNumberUnitWidth uNumberUnitWidth;
switch (currencyDisplay) {
case CurrencyDisplayOption::CODE:
uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE;
break;
case CurrencyDisplayOption::SYMBOL:
uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT;
break;
case CurrencyDisplayOption::NARROWSYMBOL:
uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW;
break;
case CurrencyDisplayOption::NAME:
uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME;
break;
default:
LOG_ECMA(FATAL) << "this branch is unreachable";
UNREACHABLE();
}
*icuNumberFormatter = icuNumberFormatter->unitWidth(uNumberUnitWidth);
}
}
if (style == StyleOption::UNIT) {
icu::MeasureUnit emptyUnit = icu::MeasureUnit();
if (icuUnit != emptyUnit) {
*icuNumberFormatter = icuNumberFormatter->unit(icuUnit);
}
if (icuPerUnit != emptyUnit) {
*icuNumberFormatter = icuNumberFormatter->perUnit(icuPerUnit);
}
}
if (style == StyleOption::CURRENCY) {
int32_t cDigits = JSNumberFormat::CurrencyDigits(currencyUStr);
fractionDigitsOption.mnfdDefault = cDigits;
fractionDigitsOption.mxfdDefault = cDigits;
} else {
fractionDigitsOption.mnfdDefault = 0;
if (style == StyleOption::PERCENT) {
fractionDigitsOption.mxfdDefault = 0;
} else {
fractionDigitsOption.mxfdDefault = 3;
}
}
return fractionDigitsOption;
}
void JSNumberFormat::InitializeNumberFormat(JSThread *thread, const JSHandle<JSNumberFormat> &numberFormat,
const JSHandle<JSTaggedValue> &locales,
const JSHandle<JSTaggedValue> &options,
bool forIcuCache)
{
EcmaVM *ecmaVm = thread->GetEcmaVM();
ObjectFactory *factory = ecmaVm->GetFactory();
JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales);
RETURN_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSObject> optionsObject;
if (options->IsUndefined()) {
optionsObject = factory->CreateNullJSObject();
} else {
optionsObject = JSTaggedValue::ToObject(thread, options);
RETURN_IF_ABRUPT_COMPLETION(thread);
}
auto globalConst = thread->GlobalConstants();
JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleMatcherString();
auto matcher = JSLocale::GetOptionOfString<LocaleMatcherOption>(
thread, optionsObject, property,
JSNumberFormat::LOCALE_MATCHER_OPTION, JSNumberFormat::LOCALE_MATCHER_OPTION_NAME,
LocaleMatcherOption::BEST_FIT);
RETURN_IF_ABRUPT_COMPLETION(thread);
property = globalConst->GetHandledNumberingSystemString();
JSHandle<JSTaggedValue> undefinedValue(thread, JSTaggedValue::Undefined());
JSHandle<JSTaggedValue> numberingSystemTaggedValue =
JSLocale::GetOption(thread, optionsObject, property, OptionType::STRING, undefinedValue, undefinedValue);
RETURN_IF_ABRUPT_COMPLETION(thread);
numberFormat->SetNumberingSystem(thread, numberingSystemTaggedValue);
std::string numberingSystemStr;
if (!numberingSystemTaggedValue->IsUndefined()) {
JSHandle<EcmaString> numberingSystemEcmaString = JSHandle<EcmaString>::Cast(numberingSystemTaggedValue);
if (EcmaStringAccessor(numberingSystemEcmaString).IsUtf16()) {
THROW_ERROR(thread, ErrorType::RANGE_ERROR, "invalid numberingSystem");
}
numberingSystemStr = intl::LocaleHelper::ConvertToStdString(thread, numberingSystemEcmaString);
if (!JSLocale::IsNormativeNumberingSystem(numberingSystemStr)) {
THROW_ERROR(thread, ErrorType::RANGE_ERROR, "invalid numberingSystem");
}
}
if (!numberingSystemStr.empty()) {
if (!JSLocale::IsWellNumberingSystem(numberingSystemStr)) {
numberFormat->SetNumberingSystem(thread, undefinedValue);
}
}
JSHandle<TaggedArray> availableLocales;
if (requestedLocales->GetLength() == 0) {
availableLocales = factory->EmptyArray();
} else {
availableLocales = GetAvailableLocales(thread);
}
std::set<std::string> relevantExtensionKeys{"nu"};
ResolvedLocale r =
JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys);
icu::Locale icuLocale = r.localeData;
JSHandle<EcmaString> localeStr = intl::LocaleHelper::ToLanguageTag(thread, icuLocale);
RETURN_IF_ABRUPT_COMPLETION(thread);
numberFormat->SetLocale(thread, localeStr.GetTaggedValue());
UErrorCode status = U_ZERO_ERROR;
if (!numberingSystemStr.empty()) {
if (JSLocale::IsWellNumberingSystem(numberingSystemStr)) {
icuLocale.setUnicodeKeywordValue("nu", numberingSystemStr, status);
ASSERT(U_SUCCESS(status));
}
}
icu::number::LocalizedNumberFormatter icuNumberFormatter =
icu::number::NumberFormatter::withLocale(icuLocale).roundingMode(UNUM_ROUND_HALFUP);
int32_t mnfdDefault = 0;
int32_t mxfdDefault = 0;
FractionDigitsOption fractionOptions =
SetNumberFormatUnitOptions(thread, numberFormat, optionsObject, &icuNumberFormatter);
RETURN_IF_ABRUPT_COMPLETION(thread);
mnfdDefault = fractionOptions.mnfdDefault;
mxfdDefault = fractionOptions.mxfdDefault;
UnitDisplayOption unitDisplay = numberFormat->GetUnitDisplay();
StyleOption style = numberFormat->GetStyle();
if (style == StyleOption::UNIT && unitDisplay != UnitDisplayOption::SHORT) {
UNumberUnitWidth uNumberUnitWidth;
switch (unitDisplay) {
case UnitDisplayOption::SHORT:
uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT;
break;
case UnitDisplayOption::NARROW:
uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW;
break;
case UnitDisplayOption::LONG:
uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME;
break;
default:
LOG_ECMA(FATAL) << "this branch is unreachable";
UNREACHABLE();
}
icuNumberFormatter = icuNumberFormatter.unitWidth(uNumberUnitWidth);
}
if (style == StyleOption::PERCENT) {
icuNumberFormatter = icuNumberFormatter.unit(icu::MeasureUnit::getPercent()).
scale(icu::number::Scale::powerOfTen(2));
}
property = globalConst->GetHandledNotationString();
auto notation = JSLocale::GetOptionOfString<NotationOption>(
thread, optionsObject, property,
JSNumberFormat::NOTATION_OPTION, JSNumberFormat::NOTATION_OPTION_NAME,
NotationOption::STANDARD);
RETURN_IF_ABRUPT_COMPLETION(thread);
numberFormat->SetNotation(notation);
JSLocale::SetNumberFormatDigitOptions(thread, numberFormat, JSHandle<JSTaggedValue>::Cast(optionsObject),
mnfdDefault, mxfdDefault, notation);
RETURN_IF_ABRUPT_COMPLETION(thread);
icuNumberFormatter = SetICUFormatterDigitOptions(thread, icuNumberFormatter, numberFormat);
property = globalConst->GetHandledCompactDisplayString();
auto compactDisplay = JSLocale::GetOptionOfString<CompactDisplayOption>(
thread, optionsObject, property,
JSNumberFormat::COMPACT_DISPLAY_OPTION, JSNumberFormat::COMPACT_DISPLAY_OPTION_NAME,
CompactDisplayOption::SHORT);
numberFormat->SetCompactDisplay(compactDisplay);
if (notation == NotationOption::COMPACT) {
switch (compactDisplay) {
case CompactDisplayOption::SHORT:
icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::compactShort());
break;
case CompactDisplayOption::LONG:
icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::compactLong());
break;
default:
break;
}
}
switch (notation) {
case NotationOption::STANDARD:
icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::simple());
break;
case NotationOption::SCIENTIFIC:
icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::scientific());
break;
case NotationOption::ENGINEERING:
icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::engineering());
break;
default:
break;
}
property = globalConst->GetHandledUserGroupingString();
bool useGrouping = false;
[[maybe_unused]] bool find = JSLocale::GetOptionOfBool(thread, optionsObject, property, true, &useGrouping);
RETURN_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> useGroupingValue(thread, JSTaggedValue(useGrouping));
numberFormat->SetUseGrouping(thread, useGroupingValue);
if (!useGrouping) {
icuNumberFormatter = icuNumberFormatter.grouping(UNumberGroupingStrategy::UNUM_GROUPING_OFF);
}
property = globalConst->GetHandledSignDisplayString();
auto signDisplay = JSLocale::GetOptionOfString<SignDisplayOption>(
thread, optionsObject, property,
JSNumberFormat::SIGN_DISPLAY_OPTION, JSNumberFormat::SIGN_DISPLAY_OPTION_NAME,
SignDisplayOption::AUTO);
RETURN_IF_ABRUPT_COMPLETION(thread);
numberFormat->SetSignDisplay(signDisplay);
CurrencySignOption currencySign = numberFormat->GetCurrencySign();
switch (signDisplay) {
case SignDisplayOption::AUTO:
if (currencySign == CurrencySignOption::ACCOUNTING) {
icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING);
} else {
icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_AUTO);
}
break;
case SignDisplayOption::NEVER:
icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_NEVER);
break;
case SignDisplayOption::ALWAYS:
if (currencySign == CurrencySignOption::ACCOUNTING) {
icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS);
} else {
icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS);
}
break;
case SignDisplayOption::EXCEPTZERO:
if (currencySign == CurrencySignOption::ACCOUNTING) {
icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO);
} else {
icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO);
}
break;
default:
break;
}
if (forIcuCache) {
std::string cacheEntry =
locales->IsUndefined() ? "" : EcmaStringAccessor(locales.GetTaggedValue()).ToStdString(thread);
auto formatterPointer = new icu::number::LocalizedNumberFormatter(icuNumberFormatter);
ecmaVm->GetIntlCache().SetIcuFormatterToCache(IcuFormatterType::NUMBER_FORMATTER, cacheEntry,
formatterPointer, JSNumberFormat::FreeIcuNumberformat);
} else {
factory->NewJSIntlIcuData(numberFormat, icuNumberFormatter, JSNumberFormat::FreeIcuNumberformat);
}
numberFormat->SetBoundFormat(thread, undefinedValue);
}
int32_t JSNumberFormat::CurrencyDigits(const icu::UnicodeString ¤cy)
{
UErrorCode status = U_ZERO_ERROR;
int32_t fractionDigits =
ucurr_getDefaultFractionDigits(reinterpret_cast<const UChar *>(currency.getBuffer()), &status);
if (U_SUCCESS(status)) {
return fractionDigits;
}
return JSNumberFormat::DEFAULT_FRACTION_DIGITS;
}
icu::number::LocalizedNumberFormatter *JSNumberFormat::GetCachedIcuNumberFormatter(JSThread *thread,
const JSHandle<JSTaggedValue> &locales)
{
std::string cacheEntry =
locales->IsUndefined() ? "" : EcmaStringAccessor(locales.GetTaggedValue()).ToStdString(thread);
void *cachedNumberFormatter = thread->GetEcmaVM()->GetIntlCache().GetIcuFormatterFromCache(
IcuFormatterType::NUMBER_FORMATTER, cacheEntry);
if (cachedNumberFormatter) {
return reinterpret_cast<icu::number::LocalizedNumberFormatter*>(cachedNumberFormatter);
}
return nullptr;
}
JSHandle<JSTaggedValue> JSNumberFormat::FormatNumeric(JSThread *thread, const JSHandle<JSNumberFormat> &numberFormat,
JSTaggedValue x)
{
icu::number::LocalizedNumberFormatter *icuNumberFormat = numberFormat->GetIcuCallTarget(thread);
ASSERT(icuNumberFormat != nullptr);
JSHandle<JSTaggedValue> res = FormatNumeric(thread, icuNumberFormat, x);
return res;
}
JSHandle<JSTaggedValue> JSNumberFormat::FormatNumeric(JSThread *thread,
const icu::number::LocalizedNumberFormatter *icuNumberFormat,
JSTaggedValue x)
{
UErrorCode status = U_ZERO_ERROR;
icu::number::FormattedNumber formattedNumber;
if (x.IsBigInt()) {
JSHandle<BigInt> bigint(thread, x);
JSHandle<EcmaString> bigintStr = BigInt::ToString(thread, bigint);
std::string stdString = EcmaStringAccessor(bigintStr).ToStdString(thread);
formattedNumber = icuNumberFormat->formatDecimal(icu::StringPiece(stdString), status);
} else {
double number = x.GetNumber();
formattedNumber = icuNumberFormat->formatDouble(number, status);
}
if (U_FAILURE(status)) {
JSHandle<JSTaggedValue> errorResult(thread, JSTaggedValue::Exception());
THROW_RANGE_ERROR_AND_RETURN(thread, "icu formatter format failed", errorResult);
}
icu::UnicodeString result = formattedNumber.toString(status);
if (U_FAILURE(status)) {
JSHandle<JSTaggedValue> errorResult(thread, JSTaggedValue::Exception());
THROW_RANGE_ERROR_AND_RETURN(thread, "formatted number toString failed", errorResult);
}
JSHandle<EcmaString> stringValue = intl::LocaleHelper::UStringToString(thread, result);
return JSHandle<JSTaggedValue>::Cast(stringValue);
}
void GroupToParts(JSThread *thread, const icu::number::FormattedNumber &formatted, const JSHandle<JSArray> &receiver,
const JSHandle<JSNumberFormat> &numberFormat, JSTaggedValue x)
{
UErrorCode status = U_ZERO_ERROR;
icu::UnicodeString formattedText = formatted.toString(status);
if (U_FAILURE(status)) {
THROW_TYPE_ERROR(thread, "formattedNumber toString failed");
}
ASSERT(x.IsNumber() || x.IsBigInt());
StyleOption styleOption = numberFormat->GetStyle();
icu::ConstrainedFieldPosition cfpo;
cfpo.constrainCategory(UFIELD_CATEGORY_NUMBER);
auto globalConst = thread->GlobalConstants();
JSMutableHandle<JSTaggedValue> typeString(thread, JSTaggedValue::Undefined());
int index = 0;
int previousLimit = 0;
* From ICU header file document @unumberformatter.h
* Sets a constraint on the field category.
*
* When this instance of ConstrainedFieldPosition is passed to FormattedValue#nextPosition,
* positions are skipped unless they have the given category.
*
* Any previously set constraints are cleared.
*
* For example, to loop over only the number-related fields:
*
* ConstrainedFieldPosition cfpo;
* cfpo.constrainCategory(UFIELDCATEGORY_NUMBER_FORMAT);
* while (fmtval.nextPosition(cfpo, status)) {
* // handle the number-related field position
* }
*/
bool lastFieldGroup = false;
int groupLeapLength = 0;
while (formatted.nextPosition(cfpo, status)) {
int32_t fieldId = cfpo.getField();
int32_t start = cfpo.getStart();
int32_t limit = cfpo.getLimit();
typeString.Update(globalConst->GetLiteralString());
if (static_cast<UNumberFormatFields>(fieldId) == UNUM_GROUPING_SEPARATOR_FIELD) {
JSHandle<EcmaString> substring =
intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, start);
typeString.Update(globalConst->GetIntegerString());
JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
RETURN_IF_ABRUPT_COMPLETION(thread);
index++;
{
typeString.Update(JSLocale::GetNumberFieldType(thread, x, fieldId).GetTaggedValue());
substring = intl::LocaleHelper::UStringToString(thread, formattedText, start, limit);
JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
RETURN_IF_ABRUPT_COMPLETION(thread);
index++;
}
lastFieldGroup = true;
groupLeapLength = start - previousLimit + 1;
previousLimit = limit;
continue;
} else if (start > previousLimit) {
JSHandle<EcmaString> substring =
intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, start);
JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
RETURN_IF_ABRUPT_COMPLETION(thread);
index++;
}
if (lastFieldGroup) {
start = start + groupLeapLength;
lastFieldGroup = false;
}
if (styleOption == StyleOption::UNIT && static_cast<UNumberFormatFields>(fieldId) == UNUM_PERCENT_FIELD) {
typeString.Update(globalConst->GetUnitString());
} else {
typeString.Update(JSLocale::GetNumberFieldType(thread, x, fieldId).GetTaggedValue());
}
JSHandle<EcmaString> substring = intl::LocaleHelper::UStringToString(thread, formattedText, start, limit);
JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
RETURN_IF_ABRUPT_COMPLETION(thread);
index++;
previousLimit = limit;
}
if (formattedText.length() > previousLimit) {
typeString.Update(globalConst->GetLiteralString());
JSHandle<EcmaString> substring =
intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, formattedText.length());
JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
RETURN_IF_ABRUPT_COMPLETION(thread);
}
}
JSHandle<JSArray> JSNumberFormat::FormatNumericToParts(JSThread *thread, const JSHandle<JSNumberFormat> &numberFormat,
JSTaggedValue x)
{
ASSERT(x.IsNumber() || x.IsBigInt());
icu::number::LocalizedNumberFormatter *icuNumberFormatter = numberFormat->GetIcuCallTarget(thread);
ASSERT(icuNumberFormatter != nullptr);
UErrorCode status = U_ZERO_ERROR;
icu::number::FormattedNumber formattedNumber;
if (x.IsBigInt()) {
JSHandle<BigInt> bigint(thread, x);
JSHandle<EcmaString> bigintStr = BigInt::ToString(thread, bigint);
std::string stdString = EcmaStringAccessor(bigintStr).ToStdString(thread);
formattedNumber = icuNumberFormatter->formatDecimal(icu::StringPiece(stdString), status);
} else {
double number = x.GetNumber();
formattedNumber = icuNumberFormatter->formatDouble(number, status);
}
if (U_FAILURE(status)) {
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<JSArray> emptyArray = factory->NewJSArray();
THROW_RANGE_ERROR_AND_RETURN(thread, "icu formatter format failed", emptyArray);
}
JSHandle<JSTaggedValue> arr = JSArray::ArrayCreate(thread, JSTaggedNumber(0));
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
JSHandle<JSArray> result = JSHandle<JSArray>::Cast(arr);
GroupToParts(thread, formattedNumber, result, numberFormat, x);
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
return result;
}
JSHandle<JSTaggedValue> JSNumberFormat::UnwrapNumberFormat(JSThread *thread, const JSHandle<JSTaggedValue> &nf)
{
ASSERT(nf->IsJSObject());
JSHandle<GlobalEnv> env = thread->GetGlobalEnv();
JSHandle<JSTaggedValue> result =
JSIntl::LegacyUnwrapReceiver(thread, nf, env->GetNumberFormatFunction(), nf->IsJSNumberFormat());
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSHandle<JSTaggedValue>(thread, JSTaggedValue::Exception()));
if (!result->IsJSNumberFormat()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "Method called on incompatible receiver",
JSHandle<JSTaggedValue>(thread, JSTaggedValue::Exception()));
}
return result;
}
JSHandle<TaggedArray> JSNumberFormat::GetAvailableLocales(JSThread *thread)
{
JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
JSHandle<JSTaggedValue> numberFormatLocales = env->GetNumberFormatLocales();
if (!numberFormatLocales->IsUndefined()) {
return JSHandle<TaggedArray>::Cast(numberFormatLocales);
}
const char *key = "NumberElements";
const char *path = nullptr;
std::vector<std::string> availableStringLocales = intl::LocaleHelper::GetAvailableLocales(thread, key, path);
JSHandle<TaggedArray> availableLocales = JSLocale::ConstructLocaleList(thread, availableStringLocales);
env->SetNumberFormatLocales(thread, availableLocales);
return availableLocales;
}
void JSNumberFormat::ResolvedOptions(JSThread *thread, const JSHandle<JSNumberFormat> &numberFormat,
const JSHandle<JSObject> &options)
{
auto globalConst = thread->GlobalConstants();
JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleString();
JSHandle<JSTaggedValue> locale(thread, numberFormat->GetLocale(thread));
JSObject::CreateDataPropertyOrThrow(thread, options, property, locale);
RETURN_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> numberingSystem(thread, numberFormat->GetNumberingSystem(thread));
if (numberingSystem->IsUndefined()) {
numberingSystem = globalConst->GetHandledLatnString();
}
property = globalConst->GetHandledNumberingSystemString();
JSObject::CreateDataPropertyOrThrow(thread, options, property, numberingSystem);
RETURN_IF_ABRUPT_COMPLETION(thread);
StyleOption style = numberFormat->GetStyle();
property = globalConst->GetHandledStyleString();
JSHandle<JSTaggedValue> styleString = OptionToEcmaString(thread, style);
JSObject::CreateDataPropertyOrThrow(thread, options, property, styleString);
RETURN_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> currency(thread, JSTaggedValue::Undefined());
if (style == StyleOption::CURRENCY) {
currency = JSHandle<JSTaggedValue>(thread, numberFormat->GetCurrency(thread));
}
if (!currency->IsUndefined()) {
property = globalConst->GetHandledCurrencyString();
JSObject::CreateDataPropertyOrThrow(thread, options, property, currency);
RETURN_IF_ABRUPT_COMPLETION(thread);
property = globalConst->GetHandledCurrencyDisplayString();
CurrencyDisplayOption currencyDisplay = numberFormat->GetCurrencyDisplay();
JSHandle<JSTaggedValue> currencyDisplayString = OptionToEcmaString(thread, currencyDisplay);
JSObject::CreateDataPropertyOrThrow(thread, options, property, currencyDisplayString);
RETURN_IF_ABRUPT_COMPLETION(thread);
property = globalConst->GetHandledCurrencySignString();
CurrencySignOption currencySign = numberFormat->GetCurrencySign();
JSHandle<JSTaggedValue> currencySignString = OptionToEcmaString(thread, currencySign);
JSObject::CreateDataPropertyOrThrow(thread, options, property, currencySignString);
RETURN_IF_ABRUPT_COMPLETION(thread);
}
if (style == StyleOption::UNIT) {
JSHandle<JSTaggedValue> unit(thread, numberFormat->GetUnit(thread));
if (!unit->IsUndefined()) {
property = globalConst->GetHandledUnitString();
JSObject::CreateDataPropertyOrThrow(thread, options, property, unit);
RETURN_IF_ABRUPT_COMPLETION(thread);
}
property = globalConst->GetHandledUnitDisplayString();
UnitDisplayOption unitDisplay = numberFormat->GetUnitDisplay();
JSHandle<JSTaggedValue> unitDisplayString = OptionToEcmaString(thread, unitDisplay);
JSObject::CreateDataPropertyOrThrow(thread, options, property, unitDisplayString);
RETURN_IF_ABRUPT_COMPLETION(thread);
}
property = globalConst->GetHandledMinimumIntegerDigitsString();
JSHandle<JSTaggedValue> minimumIntegerDigits(thread, numberFormat->GetMinimumIntegerDigits(thread));
JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumIntegerDigits);
RETURN_IF_ABRUPT_COMPLETION(thread);
RoundingType roundingType = numberFormat->GetRoundingType();
if (roundingType == RoundingType::SIGNIFICANTDIGITS) {
property = globalConst->GetHandledMinimumSignificantDigitsString();
JSHandle<JSTaggedValue> minimumSignificantDigits(thread, numberFormat->GetMinimumSignificantDigits(thread));
JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumSignificantDigits);
RETURN_IF_ABRUPT_COMPLETION(thread);
property = globalConst->GetHandledMaximumSignificantDigitsString();
JSHandle<JSTaggedValue> maximumSignificantDigits(thread, numberFormat->GetMaximumSignificantDigits(thread));
JSObject::CreateDataPropertyOrThrow(thread, options, property, maximumSignificantDigits);
RETURN_IF_ABRUPT_COMPLETION(thread);
} else {
property = globalConst->GetHandledMinimumFractionDigitsString();
JSHandle<JSTaggedValue> minimumFractionDigits(thread, numberFormat->GetMinimumFractionDigits(thread));
JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumFractionDigits);
RETURN_IF_ABRUPT_COMPLETION(thread);
property = globalConst->GetHandledMaximumFractionDigitsString();
JSHandle<JSTaggedValue> maximumFractionDigits(thread, numberFormat->GetMaximumFractionDigits(thread));
JSObject::CreateDataPropertyOrThrow(thread, options, property, maximumFractionDigits);
RETURN_IF_ABRUPT_COMPLETION(thread);
if (roundingType == RoundingType::COMPACTROUNDING) {
property = globalConst->GetHandledMinimumSignificantDigitsString();
JSHandle<JSTaggedValue> minimumSignificantDigits(thread, numberFormat->GetMinimumSignificantDigits(thread));
JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumSignificantDigits);
RETURN_IF_ABRUPT_COMPLETION(thread);
property = globalConst->GetHandledMaximumSignificantDigitsString();
JSHandle<JSTaggedValue> maximumSignificantDigits(thread, numberFormat->GetMaximumSignificantDigits(thread));
JSObject::CreateDataPropertyOrThrow(thread, options, property, maximumSignificantDigits);
RETURN_IF_ABRUPT_COMPLETION(thread);
}
}
property = globalConst->GetHandledUserGroupingString();
JSObject::CreateDataPropertyOrThrow(thread, options, property,
JSHandle<JSTaggedValue>(thread, numberFormat->GetUseGrouping(thread)));
RETURN_IF_ABRUPT_COMPLETION(thread);
property = globalConst->GetHandledNotationString();
NotationOption notation = numberFormat->GetNotation();
JSHandle<JSTaggedValue> notationString = OptionToEcmaString(thread, notation);
JSObject::CreateDataPropertyOrThrow(thread, options, property, notationString);
RETURN_IF_ABRUPT_COMPLETION(thread);
if (notation == NotationOption::COMPACT) {
property = globalConst->GetHandledCompactDisplayString();
CompactDisplayOption compactDisplay = numberFormat->GetCompactDisplay();
JSHandle<JSTaggedValue> compactDisplayString = OptionToEcmaString(thread, compactDisplay);
JSObject::CreateDataPropertyOrThrow(thread, options, property, compactDisplayString);
RETURN_IF_ABRUPT_COMPLETION(thread);
}
property = globalConst->GetHandledSignDisplayString();
SignDisplayOption signDisplay = numberFormat->GetSignDisplay();
JSHandle<JSTaggedValue> signDisplayString = OptionToEcmaString(thread, signDisplay);
JSObject::CreateDataPropertyOrThrow(thread, options, property, signDisplayString);
RETURN_IF_ABRUPT_COMPLETION(thread);
}
}