#include "config.h"
#include "IntlCollator.h"
#if ENABLE(INTL)
#include "Error.h"
#include "IntlCollatorConstructor.h"
#include "IntlObject.h"
#include "JSBoundFunction.h"
#include "JSCInlines.h"
#include "ObjectConstructor.h"
#include "SlotVisitorInlines.h"
#include "StructureInlines.h"
#include <unicode/ucol.h>
#include <wtf/unicode/Collator.h>
namespace JSC {
const ClassInfo IntlCollator::s_info = { "Object", &Base::s_info, 0, CREATE_METHOD_TABLE(IntlCollator) };
static const char* const relevantExtensionKeys[2] = { "co", "kn" };
static const size_t indexOfExtensionKeyCo = 0;
static const size_t indexOfExtensionKeyKn = 1;
void IntlCollator::UCollatorDeleter::operator()(UCollator* collator) const
{
if (collator)
ucol_close(collator);
}
IntlCollator* IntlCollator::create(VM& vm, Structure* structure)
{
IntlCollator* format = new (NotNull, allocateCell<IntlCollator>(vm.heap)) IntlCollator(vm, structure);
format->finishCreation(vm);
return format;
}
Structure* IntlCollator::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
{
return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
}
IntlCollator::IntlCollator(VM& vm, Structure* structure)
: JSDestructibleObject(vm, structure)
{
}
void IntlCollator::finishCreation(VM& vm)
{
Base::finishCreation(vm);
ASSERT(inherits(info()));
}
void IntlCollator::destroy(JSCell* cell)
{
static_cast<IntlCollator*>(cell)->IntlCollator::~IntlCollator();
}
void IntlCollator::visitChildren(JSCell* cell, SlotVisitor& visitor)
{
IntlCollator* thisObject = jsCast<IntlCollator*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor);
visitor.append(thisObject->m_boundCompare);
}
static Vector<String> sortLocaleData(const String& locale, size_t keyIndex)
{
Vector<String> keyLocaleData;
switch (keyIndex) {
case indexOfExtensionKeyCo: {
keyLocaleData.append({ });
UErrorCode status = U_ZERO_ERROR;
UEnumeration* enumeration = ucol_getKeywordValuesForLocale("collation", locale.utf8().data(), false, &status);
if (U_SUCCESS(status)) {
const char* collation;
while ((collation = uenum_next(enumeration, nullptr, &status)) && U_SUCCESS(status)) {
if (!strcmp(collation, "standard") || !strcmp(collation, "search"))
continue;
if (!strcmp(collation, "dictionary"))
collation = "dict";
else if (!strcmp(collation, "gb2312han"))
collation = "gb2312";
else if (!strcmp(collation, "phonebook"))
collation = "phonebk";
else if (!strcmp(collation, "traditional"))
collation = "trad";
keyLocaleData.append(collation);
}
uenum_close(enumeration);
}
break;
}
case indexOfExtensionKeyKn:
keyLocaleData.reserveInitialCapacity(2);
keyLocaleData.uncheckedAppend(ASCIILiteral("false"));
keyLocaleData.uncheckedAppend(ASCIILiteral("true"));
break;
default:
ASSERT_NOT_REACHED();
}
return keyLocaleData;
}
static Vector<String> searchLocaleData(const String&, size_t keyIndex)
{
Vector<String> keyLocaleData;
switch (keyIndex) {
case indexOfExtensionKeyCo:
keyLocaleData.reserveInitialCapacity(1);
keyLocaleData.append({ });
break;
case indexOfExtensionKeyKn:
keyLocaleData.reserveInitialCapacity(2);
keyLocaleData.uncheckedAppend(ASCIILiteral("false"));
keyLocaleData.uncheckedAppend(ASCIILiteral("true"));
break;
default:
ASSERT_NOT_REACHED();
}
return keyLocaleData;
}
void IntlCollator::initializeCollator(ExecState& state, JSValue locales, JSValue optionsValue)
{
VM& vm = state.vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto requestedLocales = canonicalizeLocaleList(state, locales);
RETURN_IF_EXCEPTION(scope, void());
JSObject* options;
if (optionsValue.isUndefined()) {
options = constructEmptyObject(&state);
} else { options = optionsValue.toObject(&state);
RETURN_IF_EXCEPTION(scope, void());
}
String usageString = intlStringOption(state, options, vm.propertyNames->usage, { "sort", "search" }, "usage must be either \"sort\" or \"search\"", "sort");
RETURN_IF_EXCEPTION(scope, void());
if (usageString == "sort")
m_usage = Usage::Sort;
else if (usageString == "search")
m_usage = Usage::Search;
else
ASSERT_NOT_REACHED();
Vector<String> (*localeData)(const String&, size_t);
if (m_usage == Usage::Sort)
localeData = sortLocaleData;
else
localeData = searchLocaleData;
HashMap<String, String> opt;
String matcher = intlStringOption(state, options, vm.propertyNames->localeMatcher, { "lookup", "best fit" }, "localeMatcher must be either \"lookup\" or \"best fit\"", "best fit");
RETURN_IF_EXCEPTION(scope, void());
opt.add(ASCIILiteral("localeMatcher"), matcher);
{
String numericString;
bool usesFallback;
bool numeric = intlBooleanOption(state, options, vm.propertyNames->numeric, usesFallback);
RETURN_IF_EXCEPTION(scope, void());
if (!usesFallback)
numericString = ASCIILiteral(numeric ? "true" : "false");
opt.add(ASCIILiteral("kn"), numericString);
}
{
String caseFirst = intlStringOption(state, options, vm.propertyNames->caseFirst, { "upper", "lower", "false" }, "caseFirst must be either \"upper\", \"lower\", or \"false\"", nullptr);
RETURN_IF_EXCEPTION(scope, void());
opt.add(ASCIILiteral("kf"), caseFirst);
}
auto& availableLocales = state.jsCallee()->globalObject()->intlCollatorAvailableLocales();
auto result = resolveLocale(state, availableLocales, requestedLocales, opt, relevantExtensionKeys, WTF_ARRAY_LENGTH(relevantExtensionKeys), localeData);
m_locale = result.get(ASCIILiteral("locale"));
if (m_locale.isEmpty()) {
throwTypeError(&state, scope, ASCIILiteral("failed to initialize Collator due to invalid locale"));
return;
}
const String& collation = result.get(ASCIILiteral("co"));
m_collation = collation.isNull() ? ASCIILiteral("default") : collation;
m_numeric = (result.get(ASCIILiteral("kn")) == "true");
String sensitivityString = intlStringOption(state, options, vm.propertyNames->sensitivity, { "base", "accent", "case", "variant" }, "sensitivity must be either \"base\", \"accent\", \"case\", or \"variant\"", nullptr);
RETURN_IF_EXCEPTION(scope, void());
if (sensitivityString == "base")
m_sensitivity = Sensitivity::Base;
else if (sensitivityString == "accent")
m_sensitivity = Sensitivity::Accent;
else if (sensitivityString == "case")
m_sensitivity = Sensitivity::Case;
else
m_sensitivity = Sensitivity::Variant;
bool usesFallback;
bool ignorePunctuation = intlBooleanOption(state, options, vm.propertyNames->ignorePunctuation, usesFallback);
if (usesFallback)
ignorePunctuation = false;
RETURN_IF_EXCEPTION(scope, void());
m_ignorePunctuation = ignorePunctuation;
m_initializedCollator = true;
}
void IntlCollator::createCollator(ExecState& state)
{
VM& vm = state.vm();
auto scope = DECLARE_CATCH_SCOPE(vm);
ASSERT(!m_collator);
if (!m_initializedCollator) {
initializeCollator(state, jsUndefined(), jsUndefined());
ASSERT_UNUSED(scope, !scope.exception());
}
UErrorCode status = U_ZERO_ERROR;
auto collator = std::unique_ptr<UCollator, UCollatorDeleter>(ucol_open(m_locale.utf8().data(), &status));
if (U_FAILURE(status))
return;
UColAttributeValue strength = UCOL_PRIMARY;
UColAttributeValue caseLevel = UCOL_OFF;
switch (m_sensitivity) {
case Sensitivity::Base:
break;
case Sensitivity::Accent:
strength = UCOL_SECONDARY;
break;
case Sensitivity::Case:
caseLevel = UCOL_ON;
break;
case Sensitivity::Variant:
strength = UCOL_TERTIARY;
break;
default:
ASSERT_NOT_REACHED();
}
ucol_setAttribute(collator.get(), UCOL_STRENGTH, strength, &status);
ucol_setAttribute(collator.get(), UCOL_CASE_LEVEL, caseLevel, &status);
ucol_setAttribute(collator.get(), UCOL_NUMERIC_COLLATION, m_numeric ? UCOL_ON : UCOL_OFF, &status);
ucol_setAttribute(collator.get(), UCOL_ALTERNATE_HANDLING, m_ignorePunctuation ? UCOL_SHIFTED : UCOL_DEFAULT, &status);
ucol_setAttribute(collator.get(), UCOL_NORMALIZATION_MODE, UCOL_ON, &status);
if (U_FAILURE(status))
return;
m_collator = WTFMove(collator);
}
JSValue IntlCollator::compareStrings(ExecState& state, StringView x, StringView y)
{
VM& vm = state.vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (!m_collator) {
createCollator(state);
if (!m_collator)
return throwException(&state, scope, createError(&state, ASCIILiteral("Failed to compare strings.")));
}
UErrorCode status = U_ZERO_ERROR;
UCharIterator iteratorX = createIterator(x);
UCharIterator iteratorY = createIterator(y);
auto result = ucol_strcollIter(m_collator.get(), &iteratorX, &iteratorY, &status);
if (U_FAILURE(status))
return throwException(&state, scope, createError(&state, ASCIILiteral("Failed to compare strings.")));
return jsNumber(result);
}
const char* IntlCollator::usageString(Usage usage)
{
switch (usage) {
case Usage::Sort:
return "sort";
case Usage::Search:
return "search";
}
ASSERT_NOT_REACHED();
return nullptr;
}
const char* IntlCollator::sensitivityString(Sensitivity sensitivity)
{
switch (sensitivity) {
case Sensitivity::Base:
return "base";
case Sensitivity::Accent:
return "accent";
case Sensitivity::Case:
return "case";
case Sensitivity::Variant:
return "variant";
}
ASSERT_NOT_REACHED();
return nullptr;
}
JSObject* IntlCollator::resolvedOptions(ExecState& state)
{
VM& vm = state.vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (!m_initializedCollator) {
initializeCollator(state, jsUndefined(), jsUndefined());
ASSERT_UNUSED(scope, !scope.exception());
}
JSObject* options = constructEmptyObject(&state);
options->putDirect(vm, vm.propertyNames->locale, jsString(&state, m_locale));
options->putDirect(vm, vm.propertyNames->usage, jsNontrivialString(&state, ASCIILiteral(usageString(m_usage))));
options->putDirect(vm, vm.propertyNames->sensitivity, jsNontrivialString(&state, ASCIILiteral(sensitivityString(m_sensitivity))));
options->putDirect(vm, vm.propertyNames->ignorePunctuation, jsBoolean(m_ignorePunctuation));
options->putDirect(vm, vm.propertyNames->collation, jsString(&state, m_collation));
options->putDirect(vm, vm.propertyNames->numeric, jsBoolean(m_numeric));
return options;
}
void IntlCollator::setBoundCompare(VM& vm, JSBoundFunction* format)
{
m_boundCompare.set(vm, this, format);
}
}
#endif // ENABLE(INTL)