JS8Call-Improved master
Loading...
Searching...
No Matches
Maidenhead.h
1#ifndef MAIDENHEAD_HPP__
2#define MAIDENHEAD_HPP__
3
4#include <QString>
5#include <QStringView>
6#include <QValidator>
7
8namespace Maidenhead {
9// Given a numeric Unicode value, return the upper case version if
10// it lies within the range of lower case alphabetic characters.
11//
12// A replacement for QChar::toUpper(), which isn't constexpr.
13
14constexpr char16_t normalize(char16_t const u) noexcept {
15 return (u >= u'a' && u <= u'z') ? u - (u'a' - u'A') : u;
16}
17
18static_assert(normalize(u'0') == u'0');
19static_assert(normalize(u'A') == u'A');
20static_assert(normalize(u'Z') == u'Z');
21static_assert(normalize(u'a') == u'A');
22static_assert(normalize(u'z') == u'Z');
23
24// Given a string view, return the index at which the view fails to
25// contain a valid id, or QStringView::size() if the view is valid.
26//
27// Note carefully the following:
28//
29// 1. A view that's incomplete, but still valid up to the point
30// of being incomplete, is valid.
31// 2. An odd-length view is, therefore, valid.
32// 3. A zero-length view is also valid.
33//
34// There is therfore more validation required above this point; the
35// only assertion we make on completely valid input is that it's ok
36// so far, but we're not asserting that it's complete.
37//
38// Validation is case-insensitive. While the standard defines pairs
39// containing alphabetic characters as being upper case, the older
40// QRA standard used lower case, and various software packages do
41// either or both, so we're being liberal in what we accept.
42
43constexpr auto invalidIndex(QStringView const view) noexcept {
44 // Standard Maidenhead identifiers must be exactly 4, 6 or 8
45 // characters. Indices and valid values for the pairs are:
46 //
47 // 1. Field: [0, 1]: [A, R]
48 // 2. Square: [2, 3]: [0, 9]
49 // 3. Subsquare: [4, 5]: [A, X]
50 // 4. Extended: [6, 7]: [0, 9]
51 //
52 // Nonstandard extensions exist in domains such as APRS, which
53 // add up to an additional two pairs:
54 //
55 // 5. Ultra Extended: [ 8, 9]: [A, X]
56 // 6. Hyper Extended: [10, 11]: [0, 9]
57
58 auto const size = view.size();
59
60 for (qsizetype i = 0; i < size; ++i) {
61 auto const u = normalize(view[i].unicode());
62
63 switch (i) {
64 case 0:
65 case 1:
66 if (u >= u'A' && u <= u'R')
67 continue;
68 break;
69 case 2:
70 case 3:
71 case 6:
72 case 7:
73 case 10:
74 case 11:
75 if (u >= u'0' && u <= u'9')
76 continue;
77 break;
78 case 4:
79 case 5:
80 case 8:
81 case 9:
82 if (u >= u'A' && u <= u'X')
83 continue;
84 break;
85 }
86 return i;
87 }
88
89 return size;
90}
91
92static_assert(invalidIndex(u"") == 0);
93static_assert(invalidIndex(u"S") == 0);
94static_assert(invalidIndex(u"AZ") == 1);
95static_assert(invalidIndex(u"AAA") == 2);
96static_assert(invalidIndex(u"AA00AA00AA00A") == 12);
97
98// Given a string view, return true if it has a length compatible with
99// containment of the range of pairs requested, and the data within it
100// is valid over the complete span, false otherwise.
101
102template <qsizetype Min = 2, qsizetype Max = 6>
103constexpr auto valid(QStringView const view) noexcept {
104 static_assert(Min >= 1 && Max >= 1 && Max <= 6 && Min <= Max);
105
106 if (auto const size = view.size();
107 !(size & 1) && (size >= 2 * Min) && (size <= 2 * Max)) {
108 return invalidIndex(view) == size;
109 }
110
111 return false;
112}
113
114static_assert(valid(u"AA00"));
115static_assert(valid(u"AA00AA"));
116static_assert(valid(u"AA00AA00"));
117static_assert(valid(u"BP51AD95RF"));
118static_assert(valid(u"BP51AD95RF00"));
119static_assert(valid(u"aa00"));
120static_assert(valid(u"AA00aa"));
121static_assert(valid(u"RR00XX"));
122
123static_assert(!valid(u""));
124static_assert(!valid(u"A"));
125static_assert(!valid(u"0"));
126static_assert(!valid(u"AA00 "));
127static_assert(!valid(u"AA00 "));
128static_assert(!valid(u"AA00 "));
129static_assert(!valid(u" AA00"));
130static_assert(!valid(u" AA00"));
131static_assert(!valid(u"00"));
132static_assert(!valid(u"aa00a"));
133static_assert(!valid(u"AA00ZZA"));
134static_assert(!valid(u"!@#$%^"));
135static_assert(!valid(u"123456"));
136static_assert(!valid(u"AA00ZZ"));
137static_assert(!valid(u"ss00XX"));
138static_assert(!valid(u"rr00yy"));
139static_assert(!valid(u"AAA1aa"));
140static_assert(!valid(u"BP51AD95RF00A"));
141static_assert(!valid(u"BP51AD95RF0000"));
142
143// Template specifying a QValidator, where the minimum and maximum
144// number of acceptable pairs must be specified.
145//
146// In order for an input to be acceptable, at least the minimum number
147// of pairs must be provided, no more than the maximum can be provided,
148// and all pairs must be valid.
149
150template <qsizetype Min, qsizetype Max>
151class Validator final : public QValidator {
152 static_assert(Min >= 1 && Max >= 1 && Max <= 6 && Min <= Max);
153
154 using QValidator::QValidator;
155
156 State validate(QString &input, int &pos) const override {
157 // Ensure the input is upper case and get the size.
158
159 input = input.toUpper();
160 auto const size = input.size();
161
162 // If nothing's been entered, we need more from them; if over
163 // the maximum, less.
164
165 if (size == 0)
166 return Intermediate;
167 if (size > Max * 2)
168 return Invalid;
169
170 // If anything up to the cursor is invalid, then we're invalid.
171 // Anything after the cursor, we're willing to be hopeful about.
172
173 if (auto const index = invalidIndex(input); index != size) {
174 return index < pos ? Invalid : Intermediate;
175 }
176
177 // Entire input was valid. If the count is odd, or we haven't yet
178 // hit the minimum, we need more from them, otherwise, we're good.
179
180 return ((size & 1) || (size < Min * 2)) ? Intermediate : Acceptable;
181 }
182};
183
184// Convenience definitions:
185//
186// Standard: User must specify field and square, and can optionally
187// specify subsquare. Ideal for QSO logging.
188//
189// Extended: User must specify field and square, and can optionally
190// specify subsquare, extended, ultra extended, and hyper
191// extended. Ideal for station grid entry.
192
193using StandardValidator = Validator<2, 3>;
194using ExtendedValidator = Validator<2, 6>;
195} // namespace Maidenhead
196
197#endif
Definition Maidenhead.h:151