Revision control

Copy as Markdown

// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:critical reason:data-parser
#include "qcollator_p.h"
#include "qstringlist.h"
#include "qstring.h"
#include <emscripten/val.h>
QT_BEGIN_NAMESPACE
// This backend implements collation on top of the JavaScript Intl.Collator API.
//
// Note: Intl.Collator exposes only compare(); it has no equivalent of ICU's
// sort keys. sortKey() therefore falls back to calling compare() and does
// not provide a performance benefit.
void QCollatorPrivate::init()
{
cleanup();
// Build the Intl.Collator options object from the QCollator settings.
// The Intl.Collator sensitivity option maps to case/accent handling:
// "variant" - case- and accent-sensitive (Qt::CaseSensitive)
// "accent" - accent-sensitive, case-insensitive (Qt::CaseInsensitive)
emscripten::val collatorOptions = emscripten::val::object();
if (options.testFlag(Opt::NumericSort))
collatorOptions.set("numeric", true);
collatorOptions.set("sensitivity",
options.testFlag(Opt::CaseInsensitive) ? "accent" : "variant");
if (options.testFlag(Opt::IgnorePunctuation))
collatorOptions.set("ignorePunctuation", true);
// An empty/undefined locale list makes Intl.Collator use the host's
// default locale; otherwise pass the BCP 47 tag from the QLocale.
const QString bcp47 = locale.bcp47Name();
const emscripten::val locales = bcp47.isEmpty()
? emscripten::val::undefined()
: emscripten::val(bcp47.toStdString());
const emscripten::val intl = emscripten::val::global("Intl");
collator = new emscripten::val(intl["Collator"].new_(locales, collatorOptions));
dirty = false;
}
void QCollatorPrivate::cleanup()
{
delete collator;
collator = NoCollator;
}
namespace {
// Shared comparison core for QCollator::compare() and QCollatorSortKey::compare().
// An undefined \a collator selects a plain code-unit comparison (used for the C
// locale); otherwise the strings are ordered by the given Intl.Collator. Returns a
// result normalized to the -1/0/+1 range QCollator uses.
int compareImpl(const emscripten::val &collator, QStringView s1, QStringView s2,
Qt::CaseSensitivity cs)
{
if (s1.isEmpty())
return s2.isEmpty() ? 0 : -1;
if (s2.isEmpty())
return +1;
if (collator.isUndefined())
return s1.compare(s2, cs);
const int result = collator.call<int>("compare",
s1.toString().toStdString(), s2.toString().toStdString());
return result < 0 ? -1 : (result > 0 ? 1 : 0);
}
} // unnamed namespace
int QCollator::compare(QStringView s1, QStringView s2) const
{
if (!d)
d = new QCollatorPrivate(QLocale().collation());
if (d->isC())
return compareImpl(emscripten::val::undefined(), s1, s2, caseSensitivity());
d->ensureInitialized();
return compareImpl(*d->collator, s1, s2, caseSensitivity());
}
QCollatorSortKey QCollator::sortKey(const QString &string) const
{
if (!d)
d = new QCollatorPrivate(QLocale().collation());
// Create a sort key which captures the string and the collator. Note
// that this does not provide a performance benefit, but does implement
// correct sorting.
QCollatorSortKeyWasm key;
key.string = string;
if (!d->isC()) {
d->ensureInitialized();
key.collator = *d->collator;
}
return QCollatorPrivate::sortKeyFromData(std::move(key));
}
int QCollatorSortKey::compare(const QCollatorSortKey &otherKey) const noexcept
{
return compareImpl(d->m_key.collator, d->m_key.string, otherKey.d->m_key.string, Qt::CaseSensitive);
}
QT_END_NAMESPACE