KI18n

kcountry.cpp
1/*
2 SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "kcountry.h"
8#include "isocodes_p.h"
9#include "isocodescache_p.h"
10#include "kcatalog_p.h"
11#include "klocalizedstring.h"
12#include "logging.h"
13#include "spatial_index_p.h"
14#include "timezonedata_p.h"
15
16#include <cstring>
17
18using namespace Qt::Literals;
19
20static_assert(sizeof(KCountry) == 2);
21
23 : d(0)
24{
25}
26
27KCountry::KCountry(const KCountry &) = default;
28KCountry::~KCountry() = default;
29
30KCountry &KCountry::operator=(const KCountry &) = default;
31
32bool KCountry::operator==(const KCountry &other) const
33{
34 return d == other.d;
35}
36
37bool KCountry::operator!=(const KCountry &other) const
38{
39 return d != other.d;
40}
41
43{
44 return d != 0;
45}
46
47QString KCountry::alpha2() const
48{
49 if (d == 0) {
50 return {};
51 }
52
53 QString code(2, QLatin1Char('\0'));
54 code[0] = QLatin1Char(d >> 8);
55 code[1] = QLatin1Char(d & 0xff);
56 return code;
57}
58
59QString KCountry::alpha3() const
60{
61 const auto cache = IsoCodesCache::instance();
62 const auto it = std::find_if(cache->countryAlpha3MapBegin(), cache->countryAlpha3MapEnd(), [this](auto entry) {
63 return entry.value == d;
64 });
65 if (it != cache->countryAlpha3MapEnd()) {
66 uint16_t alpha3Key = (*it).key;
67 QString code(3, QLatin1Char('\0'));
68 code[2] = QLatin1Char(IsoCodes::mapFromAlphaNumKey(alpha3Key));
69 alpha3Key /= IsoCodes::AlphaNumKeyFactor;
70 code[1] = QLatin1Char(IsoCodes::mapFromAlphaNumKey(alpha3Key));
71 alpha3Key /= IsoCodes::AlphaNumKeyFactor;
72 code[0] = QLatin1Char(IsoCodes::mapFromAlphaNumKey(alpha3Key));
73 return code;
74 }
75 return {};
76}
77
78QString KCountry::name() const
79{
80 if (d == 0) {
81 return {};
82 }
83
84 auto cache = IsoCodesCache::instance();
85 cache->loadIso3166_1();
86 const auto it = std::lower_bound(cache->countryNameMapBegin(), cache->countryNameMapEnd(), d);
87 if (it != cache->countryNameMapEnd() && (*it).key == d) {
88 return i18nd("iso_3166-1", cache->countryStringTableLookup((*it).value));
89 }
90 return {};
91}
92
93QString KCountry::emojiFlag() const
94{
95 if (d == 0) {
96 return {};
97 }
98
99 QString flag;
100 char flagA[] = "\xF0\x9F\x87\xA6";
101 flagA[3] = 0xA6 + ((d >> 8) - 'A');
102 flag += QString::fromUtf8(flagA);
103 flagA[3] = 0xA6 + ((d & 0xff) - 'A');
104 flag += QString::fromUtf8(flagA);
105 return flag;
106}
107
109{
110 if (d == 0) {
111 return QLocale::AnyCountry;
112 }
113
114 return QLocale::codeToTerritory(alpha2());
115}
116
117QList<const char *> KCountry::timeZoneIds() const
118{
120 if (d == 0) {
121 return tzs;
122 }
123
124 const auto countryIt = std::lower_bound(TimezoneData::countryTimezoneMapBegin(), TimezoneData::countryTimezoneMapEnd(), d);
125 if (countryIt != TimezoneData::countryTimezoneMapEnd() && (*countryIt).key == d) {
126 tzs.push_back(TimezoneData::ianaIdLookup((*countryIt).value));
127 return tzs;
128 }
129
130 const auto [subdivBegin, subdivEnd] =
131 std::equal_range(TimezoneData::subdivisionTimezoneMapBegin(), TimezoneData::subdivisionTimezoneMapEnd(), d, [](auto lhs, auto rhs) {
132 if constexpr (std::is_same_v<decltype(lhs), uint16_t>)
133 return lhs < (rhs.key >> 16);
134 else
135 return (lhs.key >> 16) < rhs;
136 });
137 for (auto it = subdivBegin; it != subdivEnd; ++it) {
138 const auto tzId = TimezoneData::ianaIdLookup((*it).value);
139 if (!tzs.contains(tzId)) {
140 tzs.push_back(tzId);
141 }
142 }
143
144 return tzs;
145}
146
147QString KCountry::currencyCode() const
148{
149 if (d == 0) {
150 return {};
151 }
152
153 QString currency;
155 for (const auto &l : ls) {
156 if (currency.isEmpty()) {
157 currency = l.currencySymbol(QLocale::CurrencyIsoCode);
158 } else if (currency != l.currencySymbol(QLocale::CurrencyIsoCode)) {
159 qCDebug(KI18NLD) << "conflicting currency information in QLocale for" << alpha2();
160 return {};
161 }
162 }
163 return currency;
164}
165
166QList<KCountrySubdivision> KCountry::subdivisions() const
167{
168 if (d == 0) {
169 return {};
170 }
171
173 auto cache = IsoCodesCache::instance();
174 cache->loadIso3166_2();
175 // we don't have a country->subdivisions map, instead we use the full list of subdivisions
176 // (which is sorted by country due to the country being in the two most significant bytes of its key),
177 // and check the child->parent subdivision map for root elements
178 auto it = std::lower_bound(cache->subdivisionNameMapBegin(), cache->subdivisionNameMapEnd(), d, [](auto lhs, auto rhs) {
179 return (lhs.key >> 16) < rhs;
180 });
181
182 auto [parentBegin, parentEnd] = std::equal_range(cache->subdivisionParentMapBegin(), cache->subdivisionParentMapEnd(), d, [](auto lhs, auto rhs) {
183 if constexpr (std::is_same_v<decltype(lhs), uint16_t>)
184 return lhs < (rhs.key >> 16);
185 else
186 return (lhs.key >> 16) < rhs;
187 });
188
189 for (; it != cache->subdivisionNameMapEnd() && ((*it).key >> 16) == d; ++it) {
190 if (!std::binary_search(parentBegin, parentEnd, (*it).key)) {
192 s.d = (*it).key;
193 l.push_back(s);
194 }
195 }
196
197 return l;
198}
199
200static uint16_t validatedAlpha2Key(uint16_t alpha2Key)
201{
202 if (!alpha2Key) {
203 return 0;
204 }
205
206 auto cache = IsoCodesCache::instance();
207 cache->loadIso3166_1();
208 const auto it = std::lower_bound(cache->countryNameMapBegin(), cache->countryNameMapEnd(), alpha2Key);
209 if (it != cache->countryNameMapEnd() && (*it).key == alpha2Key) {
210 return alpha2Key;
211 }
212 return 0;
213}
214
216{
217 KCountry c;
218 c.d = validatedAlpha2Key(IsoCodes::alpha2CodeToKey(alpha2Code));
219 return c;
220}
221
222KCountry KCountry::fromAlpha2(const char *alpha2Code)
223{
224 KCountry c;
225 if (!alpha2Code) {
226 return c;
227 }
228 c.d = validatedAlpha2Key(IsoCodes::alpha2CodeToKey(alpha2Code, std::strlen(alpha2Code)));
229 return c;
230}
231
232static uint16_t alpha3Lookup(uint16_t alpha3Key)
233{
234 if (!alpha3Key) {
235 return 0;
236 }
237
238 auto cache = IsoCodesCache::instance();
239 cache->loadIso3166_1();
240 const auto it = std::lower_bound(cache->countryAlpha3MapBegin(), cache->countryAlpha3MapEnd(), alpha3Key);
241 if (it != cache->countryAlpha3MapEnd() && (*it).key == alpha3Key) {
242 return (*it).value;
243 }
244 return 0;
245}
246
248{
249 KCountry c;
250 c.d = alpha3Lookup(IsoCodes::alpha3CodeToKey(alpha3Code));
251 return c;
252}
253
254KCountry KCountry::fromAlpha3(const char *alpha3Code)
255{
256 KCountry c;
257 if (!alpha3Code) {
258 return c;
259 }
260 c.d = alpha3Lookup(IsoCodes::alpha3CodeToKey(alpha3Code, std::strlen(alpha3Code)));
261 return c;
262}
263
264KCountry KCountry::fromLocation(float latitude, float longitude)
265{
266 const auto entry = SpatialIndex::lookup(latitude, longitude);
267 KCountry c;
268 c.d = entry.m_subdiv >> 16;
269 return c;
270}
271
276
277static QString normalizeCountryName(QStringView name)
278{
279 QString res;
280 res.reserve(name.size());
281 for (const auto c : name) {
282 // the following needs to be done fairly fine-grained, as this can easily mess up scripts
283 // that rely on some non-letter characters to work
284 // all values used below were obtained by similar code in KContacts, which used to do
285 // a full offline pre-computation of this and checked for ambiguities introduced by too
286 // aggressive normalization
287 switch (c.category()) {
288 // strip decorative elements that don't contribute to identification (parenthesis, dashes, quotes, etc)
296 continue;
297 default:
298 break;
299 }
300
301 if (c.isSpace()) {
302 if (!res.isEmpty() && !res.back().isSpace()) {
303 res.push_back(' '_L1);
304 }
305 continue;
306 }
307
308 // if the character has a canonical decomposition skip the combining diacritic markers following it
309 // this works particularly well for Latin, but messes up Hangul
310 if (c.script() != QChar::Script_Hangul && c.decompositionTag() == QChar::Canonical) {
311 res.push_back(c.decomposition().at(0).toCaseFolded());
312 } else {
313 res.push_back(c.toCaseFolded());
314 }
315 }
316
317 return res.trimmed();
318}
319
320// check is @p needle is a space-separated substring of haystack
321static bool isSeparatedSubstring(QStringView haystack, QStringView needle)
322{
323 auto idx = haystack.indexOf(needle);
324 if (idx < 0) {
325 return false;
326 }
327 if (idx > 0 && !haystack[idx - 1].isSpace()) {
328 return false;
329 }
330 idx += needle.size();
331 return idx >= haystack.size() || haystack[idx].isSpace();
332}
333
334static void checkSubstringMatch(QStringView lhs, QStringView rhs, uint16_t code, uint16_t &result)
335{
336 if (result == std::numeric_limits<uint16_t>::max() || result == code || rhs.isEmpty()) {
337 return;
338 }
339 const auto matches = isSeparatedSubstring(lhs, rhs) || isSeparatedSubstring(rhs, lhs);
340
341 if (!matches) {
342 return;
343 }
344 result = result == 0 ? code : std::numeric_limits<uint16_t>::max();
345}
346
348{
349 if (name.isEmpty()) {
350 return {};
351 }
352 const auto normalizedName = normalizeCountryName(name);
353
354 auto cache = IsoCodesCache::instance();
355 cache->loadIso3166_1();
356
357 uint16_t substrMatch = 0;
358
359 // check untranslated names
360 for (auto it = cache->countryNameMapBegin(); it != cache->countryNameMapEnd(); ++it) {
361 const auto normalizedCountry = normalizeCountryName(QString::fromUtf8(cache->countryStringTableLookup((*it).value)));
362 if (normalizedName == normalizedCountry) {
363 KCountry c;
364 c.d = (*it).key;
365 return c;
366 }
367 checkSubstringMatch(normalizedName, normalizedCountry, (*it).key, substrMatch);
368 }
369
370 // check translated names
371 const auto langs = KCatalog::availableCatalogLanguages("iso_3166-1");
372 for (const auto &lang : langs) {
373 const auto catalog = KCatalog("iso_3166-1", lang);
374 for (auto it = cache->countryNameMapBegin(); it != cache->countryNameMapEnd(); ++it) {
375 const auto normalizedCountry = normalizeCountryName(catalog.translate(cache->countryStringTableLookup((*it).value)));
376 if (normalizedName == normalizedCountry) {
377 KCountry c;
378 c.d = (*it).key;
379 return c;
380 }
381 checkSubstringMatch(normalizedName, normalizedCountry, (*it).key, substrMatch);
382 }
383 }
384
385 // unique prefix/suffix match
386 if (substrMatch != std::numeric_limits<uint16_t>::max() && substrMatch != 0) {
387 KCountry c;
388 c.d = substrMatch;
389 return c;
390 }
391
392 // fallback to code lookups
393 if (normalizedName.size() == 3) {
394 return fromAlpha3(normalizedName);
395 }
396 if (normalizedName.size() == 2) {
397 return fromAlpha2(normalizedName);
398 }
399
400 return {};
401}
402
404{
406 auto cache = IsoCodesCache::instance();
407 cache->loadIso3166_1();
408 l.reserve(cache->countryCount());
409 std::transform(cache->countryNameMapBegin(), cache->countryNameMapEnd(), std::back_inserter(l), [](auto entry) {
410 KCountry c;
411 c.d = entry.key;
412 return c;
413 });
414 return l;
415}
416
417QStringList KCountry::timeZoneIdsStringList() const
418{
419 const auto tzIds = timeZoneIds();
420 QStringList l;
421 l.reserve(tzIds.size());
422 std::transform(tzIds.begin(), tzIds.end(), std::back_inserter(l), [](const char *tzId) {
423 return QString::fromUtf8(tzId);
424 });
425 return l;
426}
427
428#include "moc_kcountry.cpp"
Information about an ISO 3166-2 country subdivision.
Information about an ISO 3166-1 country.
Definition kcountry.h:40
static KCountry fromLocation(float latitude, float longitude)
Looks up the country at the given geographic coordinate.
Definition kcountry.cpp:264
KCountry()
Creates an invalid/empty KCountry instance.
Definition kcountry.cpp:22
static QList< KCountry > allCountries()
List all countries.
Definition kcountry.cpp:403
bool isValid() const
Returns false if this is an empty/invalid/default constructed instance, true otherwise.
Definition kcountry.cpp:42
static KCountry fromName(QStringView name)
Attempts to identify the country from the given name.
Definition kcountry.cpp:347
static KCountry fromQLocale(QLocale::Country country)
Returns a KCountry instance matching the given QLocale::Country code.
Definition kcountry.cpp:272
QLocale::Country country() const
Returns the QLocale::Country value matching this country, or QLocale::AnyCountry if there is none.
Definition kcountry.cpp:108
static KCountry fromAlpha2(QStringView alpha2Code)
Create a KCountry instance from an ISO 3166-1 alpha 2 code.
Definition kcountry.cpp:215
static KCountry fromAlpha3(QStringView alpha3Code)
Create a KCountry instance from an ISO 3166-1 alpha 3 code.
Definition kcountry.cpp:247
QString i18nd(const char *domain, const char *text, const TYPE &arg...)
Translate a string from domain and substitute any arguments.
Punctuation_Connector
bool isSpace(char32_t ucs4)
bool contains(const AT &value) const const
void push_back(parameter_type value)
void reserve(qsizetype size)
Territory codeToTerritory(QStringView territoryCode)
QList< QLocale > matchingLocales(QLocale::Language language, QLocale::Script script, QLocale::Territory territory)
QString territoryToCode(Territory territory)
QChar & back()
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
void push_back(QChar ch)
void reserve(qsizetype size)
qsizetype size() const const
QString trimmed() const const
qsizetype indexOf(QChar c, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype size() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:10:09 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.