Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qcollator_icu.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// Copyright (C) 2013 Aleix Pol Gonzalez <aleixpol@kde.org>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:critical reason:data-parser
5
6#include "qcollator_p.h"
7#include "qlocale_p.h"
8#include "qstringlist.h"
9#include "qstring.h"
10
11#include <unicode/utypes.h>
12#include <unicode/ucol.h>
13#include <unicode/ustring.h>
14#if QT_CONFIG(icu)
15#include <unicode/ures.h>
16#endif
17
18
19#include "qdebug.h"
20#include "qlibrary.h"
21
22QT_BEGIN_NAMESPACE
23
24// There are two cases in here, using the "real" ICU (ie. QT_CONFIG(icu)) and
25// using Android's flavor of ICU provided on the system (slightly confusingly !QT_CONFIG(icu)).
26// The Android case itself consists of two variants as well, new enough versions
27// where we can dlopen the native ICU, and a JNI fallback.
28
29#if !QT_CONFIG(icu) && defined(Q_OS_ANDROID)
30using ucol_open_t = UCollator* (*)(const char*, UErrorCode*);
31using ucol_setAttribute_t = void (*)(UCollator *, UColAttribute, UColAttributeValue, UErrorCode*);
32using ucol_close_t = void (*)(UCollator*);
33using ucol_strcoll_t = UCollationResult (*)(const UCollator*, const UChar*, int32_t, const UChar*, int32_t);
34using ucol_getSortKey_t = int32_t (*)(const UCollator*, const UChar*, int32_t, uint8_t*, int32_t);
35using u_errorName_t = const char* (*)(UErrorCode);
36
37struct {
38 ucol_open_t open = nullptr;
39 ucol_setAttribute_t setAttribute = nullptr;
40 ucol_close_t close = nullptr;
41 ucol_strcoll_t strcoll = nullptr;
42 ucol_getSortKey_t getSortKey = nullptr;
43 u_errorName_t errorName = nullptr;
44} static s_ucol;
45
46#define ucol_open s_ucol.open
47#define ucol_setAttribute s_ucol.setAttribute
48#define ucol_close s_ucol.close
49#define ucol_strcoll s_ucol.strcoll
50#define ucol_getSortKey s_ucol.getSortKey
51#define u_errorName s_ucol.errorName
52#endif
53
54void QCollatorPrivate::init()
55{
56 cleanup();
57 if (isC())
58 return;
59
60#if !QT_CONFIG(icu) && defined(Q_OS_ANDROID)
61 static bool icuLoaded = []() {
62 // available on Android API 33 or higher only
63 QLibrary icuLib(QStringLiteral("libicu"));
64 if (!icuLib.load()) {
65 qWarning().nospace() << "ICU loading failed: " << icuLib.errorString()
66 << ". Fallback collator will be used instead, not all features will be available.";
67 return false;
68 }
69
70 s_ucol.open = reinterpret_cast<ucol_open_t>(icuLib.resolve("ucol_open"));
71 s_ucol.setAttribute = reinterpret_cast<ucol_setAttribute_t>(icuLib.resolve("ucol_setAttribute"));
72 s_ucol.close = reinterpret_cast<ucol_close_t>(icuLib.resolve("ucol_close"));
73 s_ucol.strcoll = reinterpret_cast<ucol_strcoll_t>(icuLib.resolve("ucol_strcoll"));
74 s_ucol.getSortKey = reinterpret_cast<ucol_getSortKey_t>(icuLib.resolve("ucol_getSortKey"));
75 s_ucol.errorName = reinterpret_cast<u_errorName_t>(icuLib.resolve("u_errorName"));
76 return s_ucol.open && s_ucol.setAttribute && s_ucol.close && s_ucol.strcoll && s_ucol.getSortKey;
77 }();
78
79 if (!icuLoaded) {
80 // on older Android versions, fall back to ICU Java API
81 // that works but has more overhead and offers less control
82 int strength = -1;
83 if (options.testFlag(Opt::DiacriticInsensitive) && options.testFlag(Opt::CaseInsensitive))
84 strength = UCOL_PRIMARY;
85 else if (options.testFlag(Opt::CaseInsensitive))
86 strength = UCOL_SECONDARY;
87 fallbackCollator = QtJniTypes::QtCollator::callStaticMethod<QtJniTypes::Collator>("getCollator", locale.bcp47Name(), strength);
88 dirty = false;
89 return;
90 }
91#endif
92
93 auto errorName = [](UErrorCode status) -> const char * {
94#if !QT_CONFIG(icu) && defined(Q_OS_ANDROID)
95 if (!s_ucol.errorName)
96 return "[Unknown ICU Error]";
97#endif
98 const char *name = u_errorName(status);
99 // Always actually non-null (ICU v78.3) but not documented as such, so make sure:
100 return name ? name : "[Unknown ICU Error]";
101 };
102
103 UErrorCode status = U_ZERO_ERROR;
104 QByteArray name = QLocalePrivate::get(locale)->bcp47Name('_');
105 collator = ucol_open(name.constData(), &status);
106 if (U_FAILURE(status)) {
107 if (status == U_FILE_ACCESS_ERROR)
108 qFatal("QCollator: ICU data file is not accessible: %s", errorName(status));
109 qCWarning(lcQCollator, "Could not create collator: %s", errorName(status));
110 collator = nullptr;
111 dirty = false;
112 return;
113 }
114
115 // enable normalization by default
116 ucol_setAttribute(collator, UCOL_NORMALIZATION_MODE, UCOL_ON, &status);
117
118 // The strength attribute in ICU is rather badly documented. Basically UCOL_PRIMARY
119 // ignores differences between base characters and accented characters as well as case.
120 // So A and A-umlaut would compare equal.
121 // UCOL_SECONDARY ignores case differences. UCOL_TERTIARY is the default in most languages
122 // and does case sensitive comparison.
123 // UCOL_QUATERNARY is used as default in a few languages such as Japanese to take care of some
124 // additional differences in those languages.
125 if (options.testFlag(Opt::DiacriticInsensitive)) {
126 // UCOL_PRIMARY ignores both diacritics and case
127 status = U_ZERO_ERROR;
128 ucol_setAttribute(collator, UCOL_STRENGTH, UCOL_PRIMARY, &status);
129 if (U_FAILURE(status)) {
130 qCWarning(lcQCollator,
131 "ucol_setAttribute: Diacritic and case insensitivity failed: %s",
132 errorName(status));
133 }
134
135 if (!options.testFlag(Opt::CaseInsensitive)) {
136 // Re-add case distinction if CaseInsensitive hasn't been set
137 status = U_ZERO_ERROR;
138 ucol_setAttribute(collator, UCOL_CASE_LEVEL, UCOL_ON, &status);
139 if (U_FAILURE(status)) {
140 qCWarning(lcQCollator,
141 "ucol_setAttribute: Diacritic insensitivity with case distinction failed:"
142 " %s", errorName(status));
143 }
144 }
145 } else {
146 const UColAttributeValue strength
147 = options.testFlag(Opt::CaseInsensitive) ? UCOL_SECONDARY : UCOL_DEFAULT_STRENGTH;
148 // Case sensitivity setting only
149 status = U_ZERO_ERROR;
150 ucol_setAttribute(collator, UCOL_STRENGTH, strength, &status);
151 if (U_FAILURE(status)) {
152 qCWarning(lcQCollator, "ucol_setAttribute: Case sensitivity failed: %s",
153 errorName(status));
154 }
155 }
156
157 status = U_ZERO_ERROR;
158 ucol_setAttribute(collator, UCOL_NUMERIC_COLLATION,
159 options.testFlag(Opt::NumericSort) ? UCOL_ON : UCOL_OFF, &status);
160 if (U_FAILURE(status)) {
161 qCWarning(lcQCollator, "ucol_setAttribute: numeric collation failed: %s",
162 errorName(status));
163 }
164
165 status = U_ZERO_ERROR;
166 ucol_setAttribute(collator, UCOL_ALTERNATE_HANDLING,
167 options.testFlag(Opt::IgnorePunctuation) ? UCOL_SHIFTED
168 : UCOL_NON_IGNORABLE, &status);
169 if (U_FAILURE(status)) {
170 qCWarning(lcQCollator, "ucol_setAttribute: Alternate handling failed: %s",
171 errorName(status));
172 }
173
174 dirty = false;
175}
176
178{
179 if (collator)
180 ucol_close(collator);
181 collator = nullptr;
182}
183
184int QCollator::compare(QStringView s1, QStringView s2) const
185{
186 if (!s1.size())
187 return s2.size() ? -1 : 0;
188 if (!s2.size())
189 return +1;
190
191 if (!d)
192 d = new QCollatorPrivate(QLocale().collation());
193
194 d->ensureInitialized();
195
196 if (d->collator) {
197 // truncating sizes (QTBUG-105038)
198 return ucol_strcoll(d->collator,
199 reinterpret_cast<const UChar *>(s1.data()), s1.size(),
200 reinterpret_cast<const UChar *>(s2.data()), s2.size());
201 }
202#if !QT_CONFIG(icu) && defined(Q_OS_ANDROID)
203 else if (d->fallbackCollator.isValid()) {
204 return d->fallbackCollator.callMethod<int>("compare", s1, s2);
205 }
206#endif
207
208 return QtPrivate::compareStrings(s1, s2, caseSensitivity());
209}
210
211QCollatorSortKey QCollator::sortKey(const QString &string) const
212{
213 if (!d)
214 d = new QCollatorPrivate(QLocale().collation());
215
216 d->ensureInitialized();
217
218 if (d->isC())
219 return QCollatorPrivate::sortKeyFromData(string.toUtf8());
220
221 if (d->collator) {
222 QByteArray result(16 + string.size() + (string.size() >> 2), Qt::Uninitialized);
223 // truncating sizes (QTBUG-105038)
224 int size = ucol_getSortKey(d->collator, (const UChar *)string.constData(),
225 string.size(), (uint8_t *)result.data(), result.size());
226 if (size > result.size()) {
227 result.resize(size);
228 size = ucol_getSortKey(d->collator, (const UChar *)string.constData(),
229 string.size(), (uint8_t *)result.data(), result.size());
230 }
231 result.truncate(size);
232 return QCollatorPrivate::sortKeyFromData(std::move(result));
233 }
234#if !QT_CONFIG(icu) && defined(Q_OS_ANDROID)
235 else if (d->fallbackCollator.isValid()) {
236 return QCollatorPrivate::sortKeyFromData(QtJniTypes::QtCollator::callStaticMethod<QByteArray>("getCollationKey", d->fallbackCollator, string));
237 }
238#endif
239
240 return QCollatorPrivate::sortKeyFromData(QByteArray());
241}
242
243int QCollatorSortKey::compare(const QCollatorSortKey &otherKey) const noexcept
244{
245 return d->m_key.compare(otherKey.d->m_key);
246}
247
248QT_END_NAMESPACE
CollatorType collator
Definition qcollator_p.h:78