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);
35
36struct {
37 ucol_open_t open = nullptr;
38 ucol_setAttribute_t setAttribute = nullptr;
39 ucol_close_t close = nullptr;
40 ucol_strcoll_t strcoll = nullptr;
41 ucol_getSortKey_t getSortKey = nullptr;
42} static s_ucol;
43
44#define ucol_open s_ucol.open
45#define ucol_setAttribute s_ucol.setAttribute
46#define ucol_close s_ucol.close
47#define ucol_strcoll s_ucol.strcoll
48#define ucol_getSortKey s_ucol.getSortKey
49#endif
50
51void QCollatorPrivate::init()
52{
53 cleanup();
54 if (isC())
55 return;
56
57#if !QT_CONFIG(icu) && defined(Q_OS_ANDROID)
58 static bool icuLoaded = []() {
59 // available on Android API 33 or higher only
60 QLibrary icuLib(QStringLiteral("libicu"));
61 if (!icuLib.load()) {
62 qWarning().nospace() << "ICU loading failed: " << icuLib.errorString()
63 << ". Fallback collator will be used instead, not all features will be available.";
64 return false;
65 }
66
67 s_ucol.open = reinterpret_cast<ucol_open_t>(icuLib.resolve("ucol_open"));
68 s_ucol.setAttribute = reinterpret_cast<ucol_setAttribute_t>(icuLib.resolve("ucol_setAttribute"));
69 s_ucol.close = reinterpret_cast<ucol_close_t>(icuLib.resolve("ucol_close"));
70 s_ucol.strcoll = reinterpret_cast<ucol_strcoll_t>(icuLib.resolve("ucol_strcoll"));
71 s_ucol.getSortKey = reinterpret_cast<ucol_getSortKey_t>(icuLib.resolve("ucol_getSortKey"));
72 return s_ucol.open && s_ucol.setAttribute && s_ucol.close && s_ucol.strcoll && s_ucol.getSortKey;
73 }();
74
75 if (!icuLoaded) {
76 // on older Android versions, fall back to ICU Java API
77 // that works but has more overhead and offers less control
78 int strength = -1;
79 if (options.testFlag(Opt::DiacriticInsensitive) && options.testFlag(Opt::CaseInsensitive))
80 strength = UCOL_PRIMARY;
81 else if (options.testFlag(Opt::CaseInsensitive))
82 strength = UCOL_SECONDARY;
83 fallbackCollator = QtJniTypes::QtCollator::callStaticMethod<QtJniTypes::Collator>("getCollator", locale.bcp47Name(), strength);
84 dirty = false;
85 return;
86 }
87#endif
88
89 UErrorCode status = U_ZERO_ERROR;
90 QByteArray name = QLocalePrivate::get(locale)->bcp47Name('_');
91 collator = ucol_open(name.constData(), &status);
92 if (U_FAILURE(status)) {
93 qWarning("Could not create collator: %d", status);
94 collator = nullptr;
95 dirty = false;
96 return;
97 }
98
99 // enable normalization by default
100 ucol_setAttribute(collator, UCOL_NORMALIZATION_MODE, UCOL_ON, &status);
101
102 // The strength attribute in ICU is rather badly documented. Basically UCOL_PRIMARY
103 // ignores differences between base characters and accented characters as well as case.
104 // So A and A-umlaut would compare equal.
105 // UCOL_SECONDARY ignores case differences. UCOL_TERTIARY is the default in most languages
106 // and does case sensitive comparison.
107 // UCOL_QUATERNARY is used as default in a few languages such as Japanese to take care of some
108 // additional differences in those languages.
109 if (options.testFlag(Opt::DiacriticInsensitive)) {
110 // UCOL_PRIMARY ignores both diacritics and case
111 status = U_ZERO_ERROR;
112 ucol_setAttribute(collator, UCOL_STRENGTH, UCOL_PRIMARY, &status);
113 if (U_FAILURE(status))
114 qWarning("ucol_setAttribute: Diacritic and case insensitivity failed: %d", status);
115
116 if (!options.testFlag(Opt::CaseInsensitive)) {
117 // Re-add case distinction if CaseInsensitive hasn't been set
118 status = U_ZERO_ERROR;
119 ucol_setAttribute(collator, UCOL_CASE_LEVEL, UCOL_ON, &status);
120 if (U_FAILURE(status)) {
121 qWarning("ucol_setAttribute: Diacritic insensitivity with case distinction failed:"
122 " %d", status);
123 }
124 }
125 } else {
126 const UColAttributeValue strength
127 = options.testFlag(Opt::CaseInsensitive) ? UCOL_SECONDARY : UCOL_DEFAULT_STRENGTH;
128 // Case sensitivity setting only
129 status = U_ZERO_ERROR;
130 ucol_setAttribute(collator, UCOL_STRENGTH, strength, &status);
131 if (U_FAILURE(status))
132 qWarning("ucol_setAttribute: Case sensitivity failed: %d", status);
133 }
134
135 status = U_ZERO_ERROR;
136 ucol_setAttribute(collator, UCOL_NUMERIC_COLLATION,
137 options.testFlag(Opt::NumericSort) ? UCOL_ON : UCOL_OFF, &status);
138 if (U_FAILURE(status))
139 qWarning("ucol_setAttribute: numeric collation failed: %d", status);
140
141 status = U_ZERO_ERROR;
142 ucol_setAttribute(collator, UCOL_ALTERNATE_HANDLING,
143 options.testFlag(Opt::IgnorePunctuation) ? UCOL_SHIFTED
144 : UCOL_NON_IGNORABLE, &status);
145 if (U_FAILURE(status))
146 qWarning("ucol_setAttribute: Alternate handling failed: %d", status);
147
148 dirty = false;
149}
150
152{
153 if (collator)
154 ucol_close(collator);
155 collator = nullptr;
156}
157
158int QCollator::compare(QStringView s1, QStringView s2) const
159{
160 if (!s1.size())
161 return s2.size() ? -1 : 0;
162 if (!s2.size())
163 return +1;
164
165 if (!d)
166 d = new QCollatorPrivate(QLocale().collation());
167
168 d->ensureInitialized();
169
170 if (d->collator) {
171 // truncating sizes (QTBUG-105038)
172 return ucol_strcoll(d->collator,
173 reinterpret_cast<const UChar *>(s1.data()), s1.size(),
174 reinterpret_cast<const UChar *>(s2.data()), s2.size());
175 }
176#if !QT_CONFIG(icu) && defined(Q_OS_ANDROID)
177 else if (d->fallbackCollator.isValid()) {
178 return d->fallbackCollator.callMethod<int>("compare", s1, s2);
179 }
180#endif
181
182 return QtPrivate::compareStrings(s1, s2, caseSensitivity());
183}
184
185QCollatorSortKey QCollator::sortKey(const QString &string) const
186{
187 if (!d)
188 d = new QCollatorPrivate(QLocale().collation());
189
190 d->ensureInitialized();
191
192 if (d->isC())
193 return QCollatorPrivate::sortKeyFromData(string.toUtf8());
194
195 if (d->collator) {
196 QByteArray result(16 + string.size() + (string.size() >> 2), Qt::Uninitialized);
197 // truncating sizes (QTBUG-105038)
198 int size = ucol_getSortKey(d->collator, (const UChar *)string.constData(),
199 string.size(), (uint8_t *)result.data(), result.size());
200 if (size > result.size()) {
201 result.resize(size);
202 size = ucol_getSortKey(d->collator, (const UChar *)string.constData(),
203 string.size(), (uint8_t *)result.data(), result.size());
204 }
205 result.truncate(size);
206 return QCollatorPrivate::sortKeyFromData(std::move(result));
207 }
208#if !QT_CONFIG(icu) && defined(Q_OS_ANDROID)
209 else if (d->fallbackCollator.isValid()) {
210 return QCollatorPrivate::sortKeyFromData(QtJniTypes::QtCollator::callStaticMethod<QByteArray>("getCollationKey", d->fallbackCollator, string));
211 }
212#endif
213
214 return QCollatorPrivate::sortKeyFromData(QByteArray());
215}
216
217int QCollatorSortKey::compare(const QCollatorSortKey &otherKey) const noexcept
218{
219 return d->m_key.compare(otherKey.d->m_key);
220}
221
222QT_END_NAMESPACE
CollatorType collator
Definition qcollator_p.h:75