IntlDisplayNames.cpp [plain text]
#include "config.h"
#include "IntlDisplayNames.h"
#include "IntlObjectInlines.h"
#include "JSCInlines.h"
#include "ObjectConstructor.h"
#include <unicode/ucurr.h>
#include <unicode/uloc.h>
#include <wtf/unicode/icu/ICUHelpers.h>
namespace JSC {
const ClassInfo IntlDisplayNames::s_info = { "Object", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(IntlDisplayNames) };
IntlDisplayNames* IntlDisplayNames::create(VM& vm, Structure* structure)
{
auto* object = new (NotNull, allocateCell<IntlDisplayNames>(vm.heap)) IntlDisplayNames(vm, structure);
object->finishCreation(vm);
return object;
}
Structure* IntlDisplayNames::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
{
return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
}
IntlDisplayNames::IntlDisplayNames(VM& vm, Structure* structure)
: Base(vm, structure)
{
}
void IntlDisplayNames::finishCreation(VM& vm)
{
Base::finishCreation(vm);
ASSERT(inherits(vm, info()));
}
void IntlDisplayNames::initializeDisplayNames(JSGlobalObject* globalObject, JSValue locales, JSValue optionsValue)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto requestedLocales = canonicalizeLocaleList(globalObject, locales);
RETURN_IF_EXCEPTION(scope, void());
JSObject* options = optionsValue.toObject(globalObject);
RETURN_IF_EXCEPTION(scope, void());
ResolveLocaleOptions localeOptions;
LocaleMatcher localeMatcher = intlOption<LocaleMatcher>(globalObject, options, vm.propertyNames->localeMatcher, { { "lookup"_s, LocaleMatcher::Lookup }, { "best fit"_s, LocaleMatcher::BestFit } }, "localeMatcher must be either \"lookup\" or \"best fit\""_s, LocaleMatcher::BestFit);
RETURN_IF_EXCEPTION(scope, void());
auto localeData = [](const String&, RelevantExtensionKey) -> Vector<String> {
return { };
};
auto& availableLocales = intlNumberFormatAvailableLocales();
auto resolved = resolveLocale(globalObject, availableLocales, requestedLocales, localeMatcher, localeOptions, { }, localeData);
m_locale = resolved.locale;
if (m_locale.isEmpty()) {
throwTypeError(globalObject, scope, "failed to initialize DisplayName due to invalid locale"_s);
return;
}
m_style = intlOption<Style>(globalObject, options, vm.propertyNames->style, { { "narrow"_s, Style::Narrow }, { "short"_s, Style::Short }, { "long"_s, Style::Long } }, "style must be either \"narrow\", \"short\", or \"long\""_s, Style::Long);
RETURN_IF_EXCEPTION(scope, void());
auto type = intlOption<Optional<Type>>(globalObject, options, vm.propertyNames->type, { { "language"_s, Type::Language }, { "region"_s, Type::Region }, { "script"_s, Type::Script }, { "currency"_s, Type::Currency } }, "type must be either \"language\", \"region\", \"script\", or \"currency\""_s, WTF::nullopt);
RETURN_IF_EXCEPTION(scope, void());
if (!type) {
throwTypeError(globalObject, scope, "type must not be undefined"_s);
return;
}
m_type = type.value();
m_fallback = intlOption<Fallback>(globalObject, options, Identifier::fromString(vm, "fallback"), { { "code"_s, Fallback::Code }, { "none"_s, Fallback::None } }, "fallback must be either \"code\" or \"none\""_s, Fallback::Code);
RETURN_IF_EXCEPTION(scope, void());
#if HAVE(ICU_U_LOCALE_DISPLAY_NAMES)
UErrorCode status = U_ZERO_ERROR;
UDisplayContext contexts[] = {
UDISPCTX_DIALECT_NAMES,
UDISPCTX_CAPITALIZATION_FOR_STANDALONE,
m_style == Style::Long ? UDISPCTX_LENGTH_FULL : UDISPCTX_LENGTH_SHORT,
UDISPCTX_NO_SUBSTITUTE,
};
m_localeCString = m_locale.utf8();
m_displayNames = std::unique_ptr<ULocaleDisplayNames, ULocaleDisplayNamesDeleter>(uldn_openForContext(m_localeCString.data(), contexts, WTF_ARRAY_LENGTH(contexts), &status));
if (U_FAILURE(status)) {
throwTypeError(globalObject, scope, "failed to initialize DisplayNames"_s);
return;
}
#else
throwTypeError(globalObject, scope, "Failed to initialize Intl.DisplayNames since used feature is not supported in the linked ICU version"_s);
return;
#endif
}
JSValue IntlDisplayNames::of(JSGlobalObject* globalObject, JSValue codeValue) const
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
#if HAVE(ICU_U_LOCALE_DISPLAY_NAMES)
ASSERT(m_displayNames);
auto code = codeValue.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, { });
Vector<UChar, 32> buffer;
UErrorCode status = U_ZERO_ERROR;
if (m_type == Type::Currency) {
if (!isWellFormedCurrencyCode(code)) {
throwRangeError(globalObject, scope, "argument is not a well-formed currency code"_s);
return { };
}
ASSERT(code.isAllASCII());
UCurrNameStyle style = UCURR_LONG_NAME;
switch (m_style) {
case Style::Long:
style = UCURR_LONG_NAME;
break;
case Style::Short:
style = UCURR_SYMBOL_NAME;
break;
case Style::Narrow:
style = UCURR_NARROW_SYMBOL_NAME;
break;
}
const UChar currency[4] = {
toASCIIUpper(code[0]),
toASCIIUpper(code[1]),
toASCIIUpper(code[2]),
u'\0'
};
int32_t length = 0;
UBool isChoiceFormat = false; const UChar* result = ucurr_getName(currency, m_localeCString.data(), style, &isChoiceFormat, &length, &status);
if (U_FAILURE(status))
return throwTypeError(globalObject, scope, "Failed to query a display name."_s);
if (status == U_USING_DEFAULT_WARNING && result == currency)
return (m_fallback == Fallback::None) ? jsUndefined() : codeValue;
return jsString(vm, String(result, length));
}
auto canonicalizeCodeForDisplayNames = [](Type type, const String& code) -> CString {
ASSERT(code.isAllASCII());
auto result = code.ascii();
char* mutableData = result.mutableData();
switch (type) {
case Type::Language: {
for (unsigned index = 0; index < result.length(); ++index)
mutableData[index] = toASCIILower(mutableData[index]);
break;
}
case Type::Region: {
for (unsigned index = 0; index < result.length(); ++index)
mutableData[index] = toASCIIUpper(mutableData[index]);
break;
}
case Type::Script: {
if (result.length() >= 1)
mutableData[0] = toASCIIUpper(mutableData[0]);
for (unsigned index = 1; index < result.length(); ++index)
mutableData[index] = toASCIILower(mutableData[index]);
break;
}
case Type::Currency:
ASSERT_NOT_REACHED();
break;
}
return result;
};
switch (m_type) {
case Type::Language: {
if (!isUnicodeLanguageId(code)) {
throwRangeError(globalObject, scope, "argument is not a language id"_s);
return { };
}
auto language = canonicalizeCodeForDisplayNames(m_type, code);
status = callBufferProducingFunction(uldn_localeDisplayName, m_displayNames.get(), language.data(), buffer);
break;
}
case Type::Region: {
if (!isUnicodeRegionSubtag(code)) {
throwRangeError(globalObject, scope, "argument is not a region subtag"_s);
return { };
}
auto region = canonicalizeCodeForDisplayNames(m_type, code);
status = callBufferProducingFunction(uldn_regionDisplayName, m_displayNames.get(), region.data(), buffer);
break;
}
case Type::Script: {
if (!isUnicodeScriptSubtag(code)) {
throwRangeError(globalObject, scope, "argument is not a script subtag"_s);
return { };
}
auto script = canonicalizeCodeForDisplayNames(m_type, code);
status = callBufferProducingFunction(uldn_scriptDisplayName, m_displayNames.get(), script.data(), buffer);
break;
}
case Type::Currency:
ASSERT_NOT_REACHED();
break;
}
if (U_FAILURE(status)) {
if (status == U_ILLEGAL_ARGUMENT_ERROR)
return (m_fallback == Fallback::None) ? jsUndefined() : codeValue;
return throwTypeError(globalObject, scope, "Failed to query a display name."_s);
}
return jsString(vm, String(buffer));
#else
UNUSED_PARAM(codeValue);
throwTypeError(globalObject, scope, "Failed to initialize Intl.DisplayNames since used feature is not supported in the linked ICU version"_s);
return { };
#endif
}
JSObject* IntlDisplayNames::resolvedOptions(JSGlobalObject* globalObject) const
{
VM& vm = globalObject->vm();
JSObject* options = constructEmptyObject(globalObject);
options->putDirect(vm, vm.propertyNames->locale, jsString(vm, m_locale));
options->putDirect(vm, vm.propertyNames->style, jsNontrivialString(vm, styleString(m_style)));
options->putDirect(vm, vm.propertyNames->type, jsNontrivialString(vm, typeString(m_type)));
options->putDirect(vm, Identifier::fromString(vm, "fallback"), jsNontrivialString(vm, fallbackString(m_fallback)));
return options;
}
ASCIILiteral IntlDisplayNames::styleString(Style style)
{
switch (style) {
case Style::Narrow:
return "narrow"_s;
case Style::Short:
return "short"_s;
case Style::Long:
return "long"_s;
}
ASSERT_NOT_REACHED();
return ASCIILiteral::null();
}
ASCIILiteral IntlDisplayNames::typeString(Type type)
{
switch (type) {
case Type::Language:
return "language"_s;
case Type::Region:
return "region"_s;
case Type::Script:
return "script"_s;
case Type::Currency:
return "currency"_s;
}
ASSERT_NOT_REACHED();
return ASCIILiteral::null();
}
ASCIILiteral IntlDisplayNames::fallbackString(Fallback fallback)
{
switch (fallback) {
case Fallback::Code:
return "code"_s;
case Fallback::None:
return "none"_s;
}
ASSERT_NOT_REACHED();
return ASCIILiteral::null();
}
}