1 | /* |
2 | SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org> |
3 | |
4 | SPDX-License-Identifier: LGPL-2.0-or-later |
5 | */ |
6 | |
7 | #ifndef ISOCODES_P_H |
8 | #define ISOCODES_P_H |
9 | |
10 | #include <QStringView> |
11 | |
12 | #include <cstdint> |
13 | |
14 | /*! |
15 | * Utilities for efficient storage of ISO codes. |
16 | * The focus on constexpr here matters, as this is used for the compiled in data tables, |
17 | * anything in there has to be constexpr in order to put the data tables into the shared read-only |
18 | * data section. |
19 | * |
20 | * There's basically two formats in here: |
21 | * - upper case + digit codes of up to 3 characters are stored as a 3 digit base 37 number, which |
22 | * fits into a 16bit value |
23 | * To simplify data table handling this is done in a way that lexicographical order is retained. |
24 | * - two letter upper case codes like ISO 3166-1 alpha2: those are stored equivalent to a const char[2] |
25 | * technically the same approach as for 3 char codes could be used as well, but that doesn't give us |
26 | * any space advantage while considerably easing debugging (codes are directly readable). |
27 | * |
28 | * \internal |
29 | */ |
30 | namespace IsoCodes |
31 | { |
32 | constexpr inline bool isAlpha(char c) |
33 | { |
34 | return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); |
35 | } |
36 | |
37 | constexpr inline bool isAlpha(QChar c) |
38 | { |
39 | return c.row() == 0 ? isAlpha(c: c.cell()) : false; |
40 | } |
41 | |
42 | constexpr inline bool isDigit(char c) |
43 | { |
44 | return c >= '0' && c <= '9'; |
45 | } |
46 | |
47 | constexpr inline bool isDigit(QChar c) |
48 | { |
49 | return c.row() == 0 ? isDigit(c: c.cell()) : false; |
50 | } |
51 | |
52 | constexpr inline uint8_t mapToUpper(char c) |
53 | { |
54 | return c >= 'a' ? c - 32 : c; |
55 | } |
56 | |
57 | constexpr inline uint8_t mapToUpper(QChar c) |
58 | { |
59 | return mapToUpper(c: c.cell()); |
60 | } |
61 | |
62 | template<typename T> |
63 | constexpr inline uint16_t alpha2CodeToKey(T code, std::size_t size) |
64 | { |
65 | return (size == 2 && isAlpha(code[0]) && isAlpha(code[1])) ? mapToUpper(code[0]) << 8 | mapToUpper(code[1]) : 0; |
66 | } |
67 | |
68 | template<std::size_t N> |
69 | constexpr inline uint16_t alpha2CodeToKey(const char (&code)[N]) |
70 | { |
71 | return alpha2CodeToKey(code, N - 1); |
72 | } |
73 | |
74 | constexpr inline uint16_t alpha2CodeToKey(QStringView code) |
75 | { |
76 | return alpha2CodeToKey(code, size: code.size()); |
77 | } |
78 | |
79 | constexpr inline uint8_t mapToAlphaNumKey(char c) |
80 | { |
81 | // this maps digits 0-9 to values 1-10, and letters to the numbers 11-36 |
82 | uint8_t key = c; |
83 | if (key <= '9') { |
84 | return key - '/'; |
85 | } else if (key >= 'a') { |
86 | key -= 32; |
87 | } |
88 | return key - 'A' + 11; |
89 | } |
90 | |
91 | constexpr inline uint8_t mapToAlphaNumKey(QChar c) |
92 | { |
93 | return mapToAlphaNumKey(c: c.cell()); |
94 | } |
95 | |
96 | enum { |
97 | AlphaNumKeyFactor = 37, |
98 | }; |
99 | |
100 | template<typename T> |
101 | constexpr inline char mapFromAlphaNumKey(T key) |
102 | { |
103 | char c = key % IsoCodes::AlphaNumKeyFactor; |
104 | if (c > 0 && c < 11) { |
105 | return c + '/'; |
106 | } |
107 | if (c >= 11) { |
108 | return c + 'A' - 11; |
109 | } |
110 | return 0; |
111 | } |
112 | |
113 | template<typename T> |
114 | constexpr inline uint16_t alphaNum3CodeToKey(T code, std::size_t size) |
115 | { |
116 | if (size > 3 || size == 0) { |
117 | return 0; |
118 | } |
119 | uint16_t key = 0; |
120 | for (std::size_t i = 0; i < size; ++i) { |
121 | if (!isAlpha(code[i]) && !isDigit(code[i])) { |
122 | return 0; |
123 | } |
124 | key *= AlphaNumKeyFactor; |
125 | key += mapToAlphaNumKey(code[i]); |
126 | } |
127 | for (std::size_t i = size; i < 3; ++i) { // "left align" to retain lexicographical order |
128 | key *= AlphaNumKeyFactor; |
129 | } |
130 | return key; |
131 | } |
132 | |
133 | constexpr inline uint16_t alphaNum3CodeToKey(QStringView code) |
134 | { |
135 | return alphaNum3CodeToKey(code, size: code.size()); |
136 | } |
137 | |
138 | template<typename T> |
139 | constexpr inline uint16_t alpha3CodeToKey(T code, std::size_t size) |
140 | { |
141 | return (size == 3 && isAlpha(code[0]) && isAlpha(code[1]) && isAlpha(code[2])) ? alphaNum3CodeToKey(code, 3) : 0; |
142 | } |
143 | |
144 | template<std::size_t N> |
145 | constexpr inline uint16_t alpha3CodeToKey(const char (&code)[N]) |
146 | { |
147 | return alpha3CodeToKey(code, N - 1); |
148 | } |
149 | |
150 | constexpr inline uint16_t alpha3CodeToKey(QStringView code) |
151 | { |
152 | return alpha3CodeToKey(code, size: code.size()); |
153 | } |
154 | |
155 | constexpr inline uint32_t subdivisionCodeToKey(const char *code, std::size_t size) |
156 | { |
157 | if (size < 4 || code[2] != '-') { |
158 | return 0; |
159 | } |
160 | |
161 | const auto countryKey = alpha2CodeToKey(code, size: 2); |
162 | const auto subdivKey = alphaNum3CodeToKey(code: code + 3, size: size - 3); |
163 | return countryKey > 0 && subdivKey > 0 ? ((uint32_t)(countryKey) << 16 | subdivKey) : 0; |
164 | } |
165 | |
166 | constexpr inline uint32_t subdivisionCodeToKey(QStringView code) |
167 | { |
168 | if (code.size() < 4 || code[2] != QLatin1Char('-')) { |
169 | return 0; |
170 | } |
171 | |
172 | const auto countryKey = alpha2CodeToKey(code: code.left(n: 2)); |
173 | const auto subdivKey = alphaNum3CodeToKey(code: code.mid(pos: 3), size: code.size() - 3); |
174 | return countryKey > 0 && subdivKey > 0 ? ((uint32_t)(countryKey) << 16 | subdivKey) : 0; |
175 | } |
176 | |
177 | template<std::size_t N> |
178 | constexpr inline uint32_t subdivisionCodeToKey(const char (&code)[N]) |
179 | { |
180 | return subdivisionCodeToKey(code, N - 1); |
181 | } |
182 | |
183 | /// Before v4.16 iso-codes used for parent code just the subdivision code, without the country |
184 | /// (only by error sometimes). Since that version the full code now is always used. |
185 | /// Handle both cases gracefully. |
186 | /// Does not check the country part for sanity, but just discards the info. |
187 | constexpr inline uint16_t parentCodeToKey(QStringView code) |
188 | { |
189 | if (code.size() < 4) { |
190 | return alphaNum3CodeToKey(code); |
191 | } |
192 | if (code[2] != QLatin1Char('-')) { |
193 | return 0; |
194 | } |
195 | |
196 | const auto countryKey = alpha2CodeToKey(code: code.left(n: 2)); |
197 | const auto subdivKey = alphaNum3CodeToKey(code: code.mid(pos: 3), size: code.size() - 3); |
198 | return countryKey > 0 ? subdivKey : 0; |
199 | } |
200 | } |
201 | |
202 | #endif // ISOCODES_P_H |
203 | |