KI18n

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

KDE's Doxygen guidelines are available online.