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 | namespace IsoCodes |
29 | { |
30 | constexpr inline bool isAlpha(char c) |
31 | { |
32 | return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); |
33 | } |
34 | |
35 | constexpr inline bool isAlpha(QChar c) |
36 | { |
37 | return c.row() == 0 ? isAlpha(c: c.cell()) : false; |
38 | } |
39 | |
40 | constexpr inline bool isDigit(char c) |
41 | { |
42 | return c >= '0' && c <= '9'; |
43 | } |
44 | |
45 | constexpr inline bool isDigit(QChar c) |
46 | { |
47 | return c.row() == 0 ? isDigit(c: c.cell()) : false; |
48 | } |
49 | |
50 | constexpr inline uint8_t mapToUpper(char c) |
51 | { |
52 | return c >= 'a' ? c - 32 : c; |
53 | } |
54 | |
55 | constexpr inline uint8_t mapToUpper(QChar c) |
56 | { |
57 | return mapToUpper(c: c.cell()); |
58 | } |
59 | |
60 | template<typename T> |
61 | constexpr inline uint16_t alpha2CodeToKey(T code, std::size_t size) |
62 | { |
63 | return (size == 2 && isAlpha(code[0]) && isAlpha(code[1])) ? mapToUpper(code[0]) << 8 | mapToUpper(code[1]) : 0; |
64 | } |
65 | |
66 | template<std::size_t N> |
67 | constexpr inline uint16_t alpha2CodeToKey(const char (&code)[N]) |
68 | { |
69 | return alpha2CodeToKey(code, N - 1); |
70 | } |
71 | |
72 | constexpr inline uint16_t alpha2CodeToKey(QStringView code) |
73 | { |
74 | return alpha2CodeToKey(code, size: code.size()); |
75 | } |
76 | |
77 | constexpr inline uint8_t mapToAlphaNumKey(char c) |
78 | { |
79 | // this maps digits 0-9 to values 1-10, and letters to the numbers 11-36 |
80 | uint8_t key = c; |
81 | if (key <= '9') { |
82 | return key - '/'; |
83 | } else if (key >= 'a') { |
84 | key -= 32; |
85 | } |
86 | return key - 'A' + 11; |
87 | } |
88 | |
89 | constexpr inline uint8_t mapToAlphaNumKey(QChar c) |
90 | { |
91 | return mapToAlphaNumKey(c: c.cell()); |
92 | } |
93 | |
94 | enum { |
95 | AlphaNumKeyFactor = 37, |
96 | }; |
97 | |
98 | template<typename T> |
99 | constexpr inline char mapFromAlphaNumKey(T key) |
100 | { |
101 | char c = key % IsoCodes::AlphaNumKeyFactor; |
102 | if (c > 0 && c < 11) { |
103 | return c + '/'; |
104 | } |
105 | if (c >= 11) { |
106 | return c + 'A' - 11; |
107 | } |
108 | return 0; |
109 | } |
110 | |
111 | template<typename T> |
112 | constexpr inline uint16_t alphaNum3CodeToKey(T code, std::size_t size) |
113 | { |
114 | if (size > 3 || size == 0) { |
115 | return 0; |
116 | } |
117 | uint16_t key = 0; |
118 | for (std::size_t i = 0; i < size; ++i) { |
119 | if (!isAlpha(code[i]) && !isDigit(code[i])) { |
120 | return 0; |
121 | } |
122 | key *= AlphaNumKeyFactor; |
123 | key += mapToAlphaNumKey(code[i]); |
124 | } |
125 | for (std::size_t i = size; i < 3; ++i) { // "left align" to retain lexicographical order |
126 | key *= AlphaNumKeyFactor; |
127 | } |
128 | return key; |
129 | } |
130 | |
131 | constexpr inline uint16_t alphaNum3CodeToKey(QStringView code) |
132 | { |
133 | return alphaNum3CodeToKey(code, size: code.size()); |
134 | } |
135 | |
136 | template<typename T> |
137 | constexpr inline uint16_t alpha3CodeToKey(T code, std::size_t size) |
138 | { |
139 | return (size == 3 && isAlpha(code[0]) && isAlpha(code[1]) && isAlpha(code[2])) ? alphaNum3CodeToKey(code, 3) : 0; |
140 | } |
141 | |
142 | template<std::size_t N> |
143 | constexpr inline uint16_t alpha3CodeToKey(const char (&code)[N]) |
144 | { |
145 | return alpha3CodeToKey(code, N - 1); |
146 | } |
147 | |
148 | constexpr inline uint16_t alpha3CodeToKey(QStringView code) |
149 | { |
150 | return alpha3CodeToKey(code, size: code.size()); |
151 | } |
152 | |
153 | constexpr inline uint32_t subdivisionCodeToKey(const char *code, std::size_t size) |
154 | { |
155 | if (size < 4 || code[2] != '-') { |
156 | return 0; |
157 | } |
158 | |
159 | const auto countryKey = alpha2CodeToKey(code, size: 2); |
160 | const auto subdivKey = alphaNum3CodeToKey(code: code + 3, size: size - 3); |
161 | return countryKey > 0 && subdivKey > 0 ? ((uint32_t)(countryKey) << 16 | subdivKey) : 0; |
162 | } |
163 | |
164 | constexpr inline uint32_t subdivisionCodeToKey(QStringView code) |
165 | { |
166 | if (code.size() < 4 || code[2] != QLatin1Char('-')) { |
167 | return 0; |
168 | } |
169 | |
170 | const auto countryKey = alpha2CodeToKey(code: code.left(n: 2)); |
171 | const auto subdivKey = alphaNum3CodeToKey(code: code.mid(pos: 3), size: code.size() - 3); |
172 | return countryKey > 0 && subdivKey > 0 ? ((uint32_t)(countryKey) << 16 | subdivKey) : 0; |
173 | } |
174 | |
175 | template<std::size_t N> |
176 | constexpr inline uint32_t subdivisionCodeToKey(const char (&code)[N]) |
177 | { |
178 | return subdivisionCodeToKey(code, N - 1); |
179 | } |
180 | |
181 | /// Before v4.16 iso-codes used for parent code just the subdivision code, without the country |
182 | /// (only by error sometimes). Since that version the full code now is always used. |
183 | /// Handle both cases gracefully. |
184 | /// Does not check the country part for sanity, but just discards the info. |
185 | constexpr inline uint16_t parentCodeToKey(QStringView code) |
186 | { |
187 | if (code.size() < 4) { |
188 | return alphaNum3CodeToKey(code); |
189 | } |
190 | if (code[2] != QLatin1Char('-')) { |
191 | return 0; |
192 | } |
193 | |
194 | const auto countryKey = alpha2CodeToKey(code: code.left(n: 2)); |
195 | const auto subdivKey = alphaNum3CodeToKey(code: code.mid(pos: 3), size: code.size() - 3); |
196 | return countryKey > 0 ? subdivKey : 0; |
197 | } |
198 | } |
199 | |
200 | #endif // ISOCODES_P_H |
201 | |