File indexing completed on 2024-04-21 03:54:29

0001 /*
0002     SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #ifndef ISOCODES_P_H
0008 #define ISOCODES_P_H
0009 
0010 #include <QStringView>
0011 
0012 #include <cstdint>
0013 
0014 /**
0015  * Utilities for efficient storage of ISO codes.
0016  * The focus on constexpr here matters, as this is used for the compiled in data tables,
0017  * anything in there has to be constexpr in order to put the data tables into the shared read-only
0018  * data section.
0019  *
0020  * There's basically two formats in here:
0021  * - upper case + digit codes of up to 3 characters are stored as a 3 digit base 37 number, which
0022  *   fits into a 16bit value
0023  *   To simplify data table handling this is done in a way that lexicographical order is retained.
0024  * - two letter upper case codes like ISO 3166-1 alpha2: those are stored equivalent to a const char[2]
0025  *   technically the same approach as for 3 char codes could be used as well, but that doesn't give us
0026  *   any space advantage while considerably easing debugging (codes are directly readable).
0027  */
0028 namespace IsoCodes
0029 {
0030 constexpr inline bool isAlpha(char c)
0031 {
0032     return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
0033 }
0034 
0035 constexpr inline bool isAlpha(QChar c)
0036 {
0037     return c.row() == 0 ? isAlpha(c.cell()) : false;
0038 }
0039 
0040 constexpr inline bool isDigit(char c)
0041 {
0042     return c >= '0' && c <= '9';
0043 }
0044 
0045 constexpr inline bool isDigit(QChar c)
0046 {
0047     return c.row() == 0 ? isDigit(c.cell()) : false;
0048 }
0049 
0050 constexpr inline uint8_t mapToUpper(char c)
0051 {
0052     return c >= 'a' ? c - 32 : c;
0053 }
0054 
0055 constexpr inline uint8_t mapToUpper(QChar c)
0056 {
0057     return mapToUpper(c.cell());
0058 }
0059 
0060 template<typename T>
0061 constexpr inline uint16_t alpha2CodeToKey(T code, std::size_t size)
0062 {
0063     return (size == 2 && isAlpha(code[0]) && isAlpha(code[1])) ? mapToUpper(code[0]) << 8 | mapToUpper(code[1]) : 0;
0064 }
0065 
0066 template<std::size_t N>
0067 constexpr inline uint16_t alpha2CodeToKey(const char (&code)[N])
0068 {
0069     return alpha2CodeToKey(code, N - 1);
0070 }
0071 
0072 constexpr inline uint16_t alpha2CodeToKey(QStringView code)
0073 {
0074     return alpha2CodeToKey(code, code.size());
0075 }
0076 
0077 constexpr inline uint8_t mapToAlphaNumKey(char c)
0078 {
0079     // this maps digits 0-9 to values 1-10, and letters to the numbers 11-36
0080     uint8_t key = c;
0081     if (key <= '9') {
0082         return key - '/';
0083     } else if (key >= 'a') {
0084         key -= 32;
0085     }
0086     return key - 'A' + 11;
0087 }
0088 
0089 constexpr inline uint8_t mapToAlphaNumKey(QChar c)
0090 {
0091     return mapToAlphaNumKey(c.cell());
0092 }
0093 
0094 enum {
0095     AlphaNumKeyFactor = 37,
0096 };
0097 
0098 template<typename T>
0099 constexpr inline char mapFromAlphaNumKey(T key)
0100 {
0101     char c = key % IsoCodes::AlphaNumKeyFactor;
0102     if (c > 0 && c < 11) {
0103         return c + '/';
0104     }
0105     if (c >= 11) {
0106         return c + 'A' - 11;
0107     }
0108     return 0;
0109 }
0110 
0111 template<typename T>
0112 constexpr inline uint16_t alphaNum3CodeToKey(T code, std::size_t size)
0113 {
0114     if (size > 3 || size == 0) {
0115         return 0;
0116     }
0117     uint16_t key = 0;
0118     for (std::size_t i = 0; i < size; ++i) {
0119         if (!isAlpha(code[i]) && !isDigit(code[i])) {
0120             return 0;
0121         }
0122         key *= AlphaNumKeyFactor;
0123         key += mapToAlphaNumKey(code[i]);
0124     }
0125     for (std::size_t i = size; i < 3; ++i) { // "left align" to retain lexicographical order
0126         key *= AlphaNumKeyFactor;
0127     }
0128     return key;
0129 }
0130 
0131 constexpr inline uint16_t alphaNum3CodeToKey(QStringView code)
0132 {
0133     return alphaNum3CodeToKey(code, code.size());
0134 }
0135 
0136 template<typename T>
0137 constexpr inline uint16_t alpha3CodeToKey(T code, std::size_t size)
0138 {
0139     return (size == 3 && isAlpha(code[0]) && isAlpha(code[1]) && isAlpha(code[2])) ? alphaNum3CodeToKey(code, 3) : 0;
0140 }
0141 
0142 template<std::size_t N>
0143 constexpr inline uint16_t alpha3CodeToKey(const char (&code)[N])
0144 {
0145     return alpha3CodeToKey(code, N - 1);
0146 }
0147 
0148 constexpr inline uint16_t alpha3CodeToKey(QStringView code)
0149 {
0150     return alpha3CodeToKey(code, code.size());
0151 }
0152 
0153 constexpr inline uint32_t subdivisionCodeToKey(const char *code, std::size_t size)
0154 {
0155     if (size < 4 || code[2] != '-') {
0156         return 0;
0157     }
0158 
0159     const auto countryKey = alpha2CodeToKey(code, 2);
0160     const auto subdivKey = alphaNum3CodeToKey(code + 3, size - 3);
0161     return countryKey > 0 && subdivKey > 0 ? ((uint32_t)(countryKey) << 16 | subdivKey) : 0;
0162 }
0163 
0164 constexpr inline uint32_t subdivisionCodeToKey(QStringView code)
0165 {
0166     if (code.size() < 4 || code[2] != QLatin1Char('-')) {
0167         return 0;
0168     }
0169 
0170     const auto countryKey = alpha2CodeToKey(code.left(2));
0171     const auto subdivKey = alphaNum3CodeToKey(code.mid(3), code.size() - 3);
0172     return countryKey > 0 && subdivKey > 0 ? ((uint32_t)(countryKey) << 16 | subdivKey) : 0;
0173 }
0174 
0175 template<std::size_t N>
0176 constexpr inline uint32_t subdivisionCodeToKey(const char (&code)[N])
0177 {
0178     return subdivisionCodeToKey(code, N - 1);
0179 }
0180 
0181 /// Before v4.16 iso-codes used for parent code just the subdivision code, without the country
0182 /// (only by error sometimes). Since that version the full code now is always used.
0183 /// Handle both cases gracefully.
0184 /// Does not check the country part for sanity, but just discards the info.
0185 constexpr inline uint16_t parentCodeToKey(QStringView code)
0186 {
0187     if (code.size() < 4) {
0188         return alphaNum3CodeToKey(code);
0189     }
0190     if (code[2] != QLatin1Char('-')) {
0191         return 0;
0192     }
0193 
0194     const auto countryKey = alpha2CodeToKey(code.left(2));
0195     const auto subdivKey = alphaNum3CodeToKey(code.mid(3), code.size() - 3);
0196     return countryKey > 0 ? subdivKey : 0;
0197 }
0198 }
0199 
0200 #endif // ISOCODES_P_H