#ifndef V8_INTL_SUPPORT
#error Internationalization is expected to be enabled.
#endif
#include "src/objects/js-collator.h"
#include "src/execution/isolate.h"
#include "src/objects/js-collator-inl.h"
#include "src/objects/js-locale.h"
#include "src/objects/managed-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/option-utils.h"
#include "unicode/coll.h"
#include "unicode/locid.h"
#include "unicode/strenum.h"
#include "unicode/ucol.h"
#include "unicode/udata.h"
#include "unicode/uloc.h"
#include "unicode/utypes.h"
namespace v8 {
namespace internal {
namespace {
enum class Usage {
SORT,
SEARCH,
};
enum class Sensitivity {
kBase,
kAccent,
kCase,
kVariant,
kUndefined,
};
enum class CaseFirst { kUndefined, kUpper, kLower, kFalse };
Maybe<CaseFirst> GetCaseFirst(Isolate* isolate,
DirectHandle<JSReceiver> options,
const char* method_name) {
return GetStringOption<CaseFirst>(
isolate, options, isolate->factory()->caseFirst_string(), method_name,
std::to_array<const std::string_view>({"upper", "lower", "false"}),
std::array{CaseFirst::kUpper, CaseFirst::kLower, CaseFirst::kFalse},
CaseFirst::kUndefined);
}
void CreateDataPropertyForOptions(Isolate* isolate,
DirectHandle<JSObject> options,
DirectHandle<String> key, const char* value) {
DCHECK_NOT_NULL(value);
DirectHandle<String> value_str =
isolate->factory()->NewStringFromAsciiChecked(value);
Maybe<bool> maybe = JSReceiver::CreateDataProperty(
isolate, options, key, value_str, Just(kDontThrow));
DCHECK(maybe.FromJust());
USE(maybe);
}
void CreateDataPropertyForOptions(Isolate* isolate,
DirectHandle<JSObject> options,
DirectHandle<String> key, bool value) {
DirectHandle<Object> value_obj = isolate->factory()->ToBoolean(value);
Maybe<bool> maybe = JSReceiver::CreateDataProperty(
isolate, options, key, value_obj, Just(kDontThrow));
DCHECK(maybe.FromJust());
USE(maybe);
}
}
DirectHandle<JSObject> JSCollator::ResolvedOptions(
Isolate* isolate, DirectHandle<JSCollator> collator) {
DirectHandle<JSObject> options =
isolate->factory()->NewJSObject(isolate->object_function());
icu::Collator* icu_collator = collator->icu_collator()->raw();
DCHECK_NOT_NULL(icu_collator);
UErrorCode status = U_ZERO_ERROR;
bool numeric =
icu_collator->getAttribute(UCOL_NUMERIC_COLLATION, status) == UCOL_ON;
DCHECK(U_SUCCESS(status));
const char* case_first = nullptr;
status = U_ZERO_ERROR;
switch (icu_collator->getAttribute(UCOL_CASE_FIRST, status)) {
case UCOL_LOWER_FIRST:
case_first = "lower";
break;
case UCOL_UPPER_FIRST:
case_first = "upper";
break;
default:
case_first = "false";
}
DCHECK(U_SUCCESS(status));
const char* sensitivity = nullptr;
status = U_ZERO_ERROR;
switch (icu_collator->getAttribute(UCOL_STRENGTH, status)) {
case UCOL_PRIMARY: {
DCHECK(U_SUCCESS(status));
status = U_ZERO_ERROR;
if (UCOL_ON == icu_collator->getAttribute(UCOL_CASE_LEVEL, status)) {
sensitivity = "case";
} else {
sensitivity = "base";
}
DCHECK(U_SUCCESS(status));
break;
}
case UCOL_SECONDARY:
sensitivity = "accent";
break;
case UCOL_TERTIARY:
sensitivity = "variant";
break;
case UCOL_QUATERNARY:
sensitivity = "variant";
break;
default:
sensitivity = "variant";
}
DCHECK(U_SUCCESS(status));
status = U_ZERO_ERROR;
bool ignore_punctuation = icu_collator->getAttribute(UCOL_ALTERNATE_HANDLING,
status) == UCOL_SHIFTED;
DCHECK(U_SUCCESS(status));
status = U_ZERO_ERROR;
icu::Locale icu_locale(icu_collator->getLocale(ULOC_VALID_LOCALE, status));
DCHECK(U_SUCCESS(status));
const char* collation = "default";
const char* usage = "sort";
const char* collation_key = "co";
status = U_ZERO_ERROR;
std::string collation_value =
icu_locale.getUnicodeKeywordValue<std::string>(collation_key, status);
std::string locale;
if (U_SUCCESS(status)) {
if (collation_value == "search") {
usage = "search";
collation = "default";
icu::Locale new_icu_locale = icu_locale;
status = U_ZERO_ERROR;
new_icu_locale.setUnicodeKeywordValue(collation_key, nullptr, status);
DCHECK(U_SUCCESS(status));
locale = Intl::ToLanguageTag(new_icu_locale).FromJust();
} else {
collation = collation_value.c_str();
locale = Intl::ToLanguageTag(icu_locale).FromJust();
}
} else {
status = U_ZERO_ERROR;
std::unique_ptr<icu::StringEnumeration> enumeration(
icu::Collator::getKeywordValuesForLocale("collation", icu_locale, true,
status));
if (U_SUCCESS(status)) {
const char* item = enumeration->next(nullptr, status);
if (U_SUCCESS(status) && std::strcmp(item, "standard") != 0) {
collation_value = item;
collation = collation_value.c_str();
}
}
locale = Intl::ToLanguageTag(icu_locale).FromJust();
}
if (collator->locale()->length() != 0) {
DirectHandle<String> locale_from_collator(collator->locale(), isolate);
Maybe<bool> maybe = JSReceiver::CreateDataProperty(
isolate, options, isolate->factory()->locale_string(),
locale_from_collator, Just(kDontThrow));
DCHECK(maybe.FromJust());
USE(maybe);
} else {
CreateDataPropertyForOptions(
isolate, options, isolate->factory()->locale_string(), locale.c_str());
}
CreateDataPropertyForOptions(isolate, options,
isolate->factory()->usage_string(), usage);
CreateDataPropertyForOptions(
isolate, options, isolate->factory()->sensitivity_string(), sensitivity);
CreateDataPropertyForOptions(isolate, options,
isolate->factory()->ignorePunctuation_string(),
ignore_punctuation);
CreateDataPropertyForOptions(
isolate, options, isolate->factory()->collation_string(), collation);
CreateDataPropertyForOptions(isolate, options,
isolate->factory()->numeric_string(), numeric);
CreateDataPropertyForOptions(
isolate, options, isolate->factory()->caseFirst_string(), case_first);
return options;
}
namespace {
CaseFirst ToCaseFirst(const char* str) {
if (strcmp(str, "upper") == 0) return CaseFirst::kUpper;
if (strcmp(str, "lower") == 0) return CaseFirst::kLower;
if (strcmp(str, "false") == 0) return CaseFirst::kFalse;
return CaseFirst::kUndefined;
}
UColAttributeValue ToUColAttributeValue(CaseFirst case_first) {
switch (case_first) {
case CaseFirst::kUpper:
return UCOL_UPPER_FIRST;
case CaseFirst::kLower:
return UCOL_LOWER_FIRST;
case CaseFirst::kFalse:
case CaseFirst::kUndefined:
return UCOL_OFF;
}
}
void SetNumericOption(icu::Collator* icu_collator, bool numeric) {
DCHECK_NOT_NULL(icu_collator);
UErrorCode status = U_ZERO_ERROR;
icu_collator->setAttribute(UCOL_NUMERIC_COLLATION,
numeric ? UCOL_ON : UCOL_OFF, status);
DCHECK(U_SUCCESS(status));
}
void SetCaseFirstOption(icu::Collator* icu_collator, CaseFirst case_first) {
DCHECK_NOT_NULL(icu_collator);
UErrorCode status = U_ZERO_ERROR;
icu_collator->setAttribute(UCOL_CASE_FIRST, ToUColAttributeValue(case_first),
status);
DCHECK(U_SUCCESS(status));
}
}
MaybeHandle<JSCollator> JSCollator::New(Isolate* isolate, DirectHandle<Map> map,
DirectHandle<Object> locales,
DirectHandle<Object> options_obj,
const char* service) {
Maybe<std::vector<std::string>> maybe_requested_locales =
Intl::CanonicalizeLocaleList(isolate, locales);
MAYBE_RETURN(maybe_requested_locales, Handle<JSCollator>());
std::vector<std::string> requested_locales =
maybe_requested_locales.FromJust();
DirectHandle<JSReceiver> options;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options, CoerceOptionsToObject(isolate, options_obj, service));
Maybe<Usage> maybe_usage = GetStringOption<Usage>(
isolate, options, isolate->factory()->usage_string(), service,
std::to_array<const std::string_view>({"sort", "search"}),
std::array{Usage::SORT, Usage::SEARCH}, Usage::SORT);
MAYBE_RETURN(maybe_usage, MaybeHandle<JSCollator>());
Usage usage = maybe_usage.FromJust();
Maybe<Intl::MatcherOption> maybe_locale_matcher =
Intl::GetLocaleMatcher(isolate, options, service);
MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSCollator>());
Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
DirectHandle<String> collation_str;
Maybe<bool> maybe_collation =
GetStringOption(isolate, options, isolate->factory()->collation_string(),
service, &collation_str);
MAYBE_RETURN(maybe_collation, MaybeHandle<JSCollator>());
std::string collation_stdstr;
if (maybe_collation.FromJust()) {
collation_stdstr = collation_str->ToStdString();
if (!JSLocale::Is38AlphaNumList(collation_stdstr)) {
THROW_NEW_ERROR(
isolate,
NewRangeError(MessageTemplate::kInvalid,
isolate->factory()->collation_string(), collation_str));
}
}
bool numeric;
Maybe<bool> found_numeric =
GetBoolOption(isolate, options, isolate->factory()->numeric_string(),
service, &numeric);
MAYBE_RETURN(found_numeric, MaybeHandle<JSCollator>());
Maybe<CaseFirst> maybe_case_first = GetCaseFirst(isolate, options, service);
MAYBE_RETURN(maybe_case_first, MaybeHandle<JSCollator>());
CaseFirst case_first = maybe_case_first.FromJust();
Intl::ResolvedLocale r;
if (!Intl::ResolveLocale(isolate, JSCollator::GetAvailableLocales(),
requested_locales, matcher, {"co", "kn", "kf"})
.To(&r)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError));
}
icu::Locale icu_locale = r.icu_locale;
DCHECK(!icu_locale.isBogus());
UErrorCode status = U_ZERO_ERROR;
if (maybe_collation.FromJust()) {
auto co_extension_it = r.extensions.find("co");
if (co_extension_it != r.extensions.end() &&
co_extension_it->second != collation_stdstr &&
Intl::IsValidCollation(icu_locale, collation_stdstr)) {
icu_locale.setUnicodeKeywordValue("co", nullptr, status);
DCHECK(U_SUCCESS(status));
}
}
icu::Locale data_locale(icu_locale);
if (usage == Usage::SEARCH) {
UErrorCode set_status = U_ZERO_ERROR;
data_locale.setUnicodeKeywordValue("co", "search", set_status);
DCHECK(U_SUCCESS(set_status));
} else {
if (maybe_collation.FromJust() &&
Intl::IsValidCollation(icu_locale, collation_stdstr)) {
data_locale.setUnicodeKeywordValue("co", collation_stdstr, status);
if (r.extensions.find("co") == r.extensions.end()) {
icu_locale.setUnicodeKeywordValue("co", collation_stdstr, status);
DCHECK(U_SUCCESS(status));
}
}
}
std::unique_ptr<icu::Collator> icu_collator(
icu::Collator::createInstance(data_locale, status));
if (U_FAILURE(status) || icu_collator == nullptr) {
status = U_ZERO_ERROR;
icu::Locale no_extension_locale(data_locale.getBaseName());
icu_collator.reset(
icu::Collator::createInstance(no_extension_locale, status));
if (U_FAILURE(status) || icu_collator == nullptr) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError));
}
}
DCHECK(U_SUCCESS(status));
icu::Locale collator_locale(
icu_collator->getLocale(ULOC_VALID_LOCALE, status));
status = U_ZERO_ERROR;
auto kn_extension_it = r.extensions.find("kn");
if (found_numeric.FromJust()) {
SetNumericOption(icu_collator.get(), numeric);
if (kn_extension_it != r.extensions.end() &&
kn_extension_it->second == "false") {
icu_locale.setUnicodeKeywordValue("kn", nullptr, status);
DCHECK(U_SUCCESS(status));
}
} else {
if (kn_extension_it != r.extensions.end()) {
SetNumericOption(icu_collator.get(), (kn_extension_it->second == "true"));
}
}
auto kf_extension_it = r.extensions.find("kf");
if (case_first != CaseFirst::kUndefined) {
SetCaseFirstOption(icu_collator.get(), case_first);
if (kf_extension_it != r.extensions.end() &&
((case_first == CaseFirst::kLower &&
kf_extension_it->second != "lower") ||
(case_first == CaseFirst::kUpper &&
kf_extension_it->second != "upper"))) {
icu_locale.setUnicodeKeywordValue("kf", nullptr, status);
DCHECK(U_SUCCESS(status));
}
} else {
if (kf_extension_it != r.extensions.end()) {
SetCaseFirstOption(icu_collator.get(),
ToCaseFirst(kf_extension_it->second.c_str()));
}
}
status = U_ZERO_ERROR;
icu_collator->setAttribute(UCOL_NORMALIZATION_MODE, UCOL_ON, status);
DCHECK(U_SUCCESS(status));
Maybe<Sensitivity> maybe_sensitivity = GetStringOption<Sensitivity>(
isolate, options, isolate->factory()->sensitivity_string(), service,
std::to_array<const std::string_view>(
{"base", "accent", "case", "variant"}),
std::array{Sensitivity::kBase, Sensitivity::kAccent, Sensitivity::kCase,
Sensitivity::kVariant},
Sensitivity::kUndefined);
MAYBE_RETURN(maybe_sensitivity, MaybeHandle<JSCollator>());
Sensitivity sensitivity = maybe_sensitivity.FromJust();
if (sensitivity == Sensitivity::kUndefined) {
if (usage == Usage::SORT) {
sensitivity = Sensitivity::kVariant;
}
}
switch (sensitivity) {
case Sensitivity::kBase:
icu_collator->setStrength(icu::Collator::PRIMARY);
break;
case Sensitivity::kAccent:
icu_collator->setStrength(icu::Collator::SECONDARY);
break;
case Sensitivity::kCase:
icu_collator->setStrength(icu::Collator::PRIMARY);
status = U_ZERO_ERROR;
icu_collator->setAttribute(UCOL_CASE_LEVEL, UCOL_ON, status);
DCHECK(U_SUCCESS(status));
break;
case Sensitivity::kVariant:
icu_collator->setStrength(icu::Collator::TERTIARY);
break;
case Sensitivity::kUndefined:
break;
}
bool ignore_punctuation = false;
Maybe<bool> found_ignore_punctuation = GetBoolOption(
isolate, options, isolate->factory()->ignorePunctuation_string(), service,
&ignore_punctuation);
MAYBE_RETURN(found_ignore_punctuation, MaybeHandle<JSCollator>());
if (found_ignore_punctuation.FromJust()) {
status = U_ZERO_ERROR;
icu_collator->setAttribute(
UCOL_ALTERNATE_HANDLING,
ignore_punctuation ? UCOL_SHIFTED : UCOL_NON_IGNORABLE, status);
DCHECK(U_SUCCESS(status));
}
DirectHandle<Managed<icu::Collator>> managed_collator =
Managed<icu::Collator>::From(isolate, 0, std::move(icu_collator));
Maybe<std::string> maybe_locale_str = Intl::ToLanguageTag(icu_locale);
MAYBE_RETURN(maybe_locale_str, MaybeHandle<JSCollator>());
DirectHandle<String> locale_str =
isolate->factory()->NewStringFromAsciiChecked(
maybe_locale_str.FromJust().c_str());
Handle<JSCollator> collator =
Cast<JSCollator>(isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
DisallowGarbageCollection no_gc;
collator->set_icu_collator(*managed_collator);
collator->set_locale(*locale_str);
return collator;
}
namespace {
class CollatorAvailableLocales {
public:
CollatorAvailableLocales() {
int32_t num_locales = 0;
const icu::Locale* icu_available_locales =
icu::Collator::getAvailableLocales(num_locales);
std::vector<std::string> locales;
locales.reserve(num_locales);
for (int32_t i = 0; i < num_locales; ++i) {
locales.push_back(
Intl::ToLanguageTag(icu_available_locales[i]).FromJust());
}
#define U_ICUDATA_COLL U_ICUDATA_NAME U_TREE_SEPARATOR_STRING "coll"
set_ = Intl::BuildLocaleSet(locales, U_ICUDATA_COLL, nullptr);
#undef U_ICUDATA_COLL
}
virtual ~CollatorAvailableLocales() = default;
const std::set<std::string>& Get() const { return set_; }
private:
std::set<std::string> set_;
};
}
const std::set<std::string>& JSCollator::GetAvailableLocales() {
static base::LazyInstance<CollatorAvailableLocales>::type available_locales =
LAZY_INSTANCE_INITIALIZER;
return available_locales.Pointer()->Get();
}
}
}