File: Maidenhead.h

package info (click to toggle)
js8call 2.5.2%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 24,720 kB
  • sloc: cpp: 562,651; sh: 898; python: 132; ansic: 102; makefile: 4
file content (212 lines) | stat: -rw-r--r-- 6,598 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
#ifndef MAIDENHEAD_HPP__
#define MAIDENHEAD_HPP__

#include <QString>
#include <QStringView>
#include <QValidator>

namespace Maidenhead
{
  // Given a numeric Unicode value, return the upper case version if
  // it lies within the range of lower case alphabetic characters.
  //
  // A replacement for QChar::toUpper(), which isn't constexpr.

  constexpr char16_t
  normalize(char16_t const u) noexcept
  {
    return (u >= u'a' && u <= u'z')
         ?  u - (u'a' - u'A')
         :  u;
  }

  static_assert(normalize(u'0') == u'0');
  static_assert(normalize(u'A') == u'A');
  static_assert(normalize(u'Z') == u'Z');
  static_assert(normalize(u'a') == u'A');
  static_assert(normalize(u'z') == u'Z');

  // Given a string view, return the index at which the view fails to
  // contain a valid id, or QStringView::size() if the view is valid.
  //
  // Note carefully the following:
  //
  //    1. A view that's incomplete, but still valid up to the point
  //       of being incomplete, is valid.
  //    2. An odd-length view is, therefore, valid.
  //    3. A zero-length view is also valid.
  //
  // There is therfore more validation required above this point; the
  // only assertion we make on completely valid input is that it's ok
  // so far, but we're not asserting that it's complete.
  //
  // Validation is case-insensitive. While the standard defines pairs
  // containing alphabetic characters as being upper case, the older
  // QRA standard used lower case, and various software packages do
  // either or both, so we're being liberal in what we accept.

  constexpr auto
  invalidIndex(QStringView const view) noexcept
  {
    // Standard Maidenhead identifiers must be exactly 4, 6 or 8
    // characters. Indices and valid values for the pairs are:
    //
    //   1. Field:     [0, 1]: [A, R]
    //   2. Square:    [2, 3]: [0, 9]
    //   3. Subsquare: [4, 5]: [A, X]
    //   4. Extended:  [6, 7]: [0, 9]
    //
    // Nonstandard extensions exist in domains such as APRS, which
    // add up to an additional two pairs:
    //
    //   5. Ultra Extended: [ 8,  9]: [A, X]
    //   6. Hyper Extended: [10, 11]: [0, 9]

    auto const size = view.size();

    for (qsizetype i = 0; i < size; ++i)
    {
      auto const u = normalize(view[i].unicode());

      switch (i)
      {
        case  0: case  1: if (u >= u'A' && u <= u'R') continue; break;
        case  2: case  3:
        case  6: case  7:
        case 10: case 11: if (u >= u'0' && u <= u'9') continue; break;
        case  4: case  5:
        case  8: case  9: if (u >= u'A' && u <= u'X') continue; break;
      }
      return i;
    }

    return size;
  }

  static_assert(invalidIndex(u"")              == 0);
  static_assert(invalidIndex(u"S")             == 0);
  static_assert(invalidIndex(u"AZ")            == 1);
  static_assert(invalidIndex(u"AAA")           == 2);
  static_assert(invalidIndex(u"AA00AA00AA00A") == 12);

  // Given a string view, return true if it has a length compatible with
  // containment of the range of pairs requested, and the data within it
  // is valid over the complete span, false otherwise.

  template <qsizetype Min = 2,
            qsizetype Max = 6>
  constexpr auto
  valid(QStringView const view) noexcept
  {
    static_assert(Min >= 1 &&
                  Max >= 1 &&
                  Max <= 6 &&
                  Min <= Max);

    if (auto const size = view.size();
                 !(size  & 1)       &&
                  (size >= 2 * Min) &&
                  (size <= 2 * Max))
    {
      return invalidIndex(view) == size;
    }

    return false;
  }

  static_assert(valid(u"AA00"));
  static_assert(valid(u"AA00AA"));
  static_assert(valid(u"AA00AA00"));
  static_assert(valid(u"BP51AD95RF"));
  static_assert(valid(u"BP51AD95RF00"));
  static_assert(valid(u"aa00"));
  static_assert(valid(u"AA00aa"));
  static_assert(valid(u"RR00XX"));

  static_assert(!valid(u""));
  static_assert(!valid(u"A"));
  static_assert(!valid(u"0"));
  static_assert(!valid(u"AA00 "));
  static_assert(!valid(u"AA00 "));
  static_assert(!valid(u"AA00 "));
  static_assert(!valid(u" AA00"));
  static_assert(!valid(u" AA00"));
  static_assert(!valid(u"00"));
  static_assert(!valid(u"aa00a"));
  static_assert(!valid(u"AA00ZZA"));
  static_assert(!valid(u"!@#$%^"));
  static_assert(!valid(u"123456"));
  static_assert(!valid(u"AA00ZZ"));
  static_assert(!valid(u"ss00XX"));
  static_assert(!valid(u"rr00yy"));
  static_assert(!valid(u"AAA1aa"));
  static_assert(!valid(u"BP51AD95RF00A"));
  static_assert(!valid(u"BP51AD95RF0000"));

  // Template specifying a QValidator, where the minimum and maximum
  // number of acceptable pairs must be specified.
  //
  // In order for an input to be acceptable, at least the minimum number
  // of pairs must be provided, no more than the maximum can be provided,
  // and all pairs must be valid.

  template <qsizetype Min,
            qsizetype Max>
  class Validator final : public QValidator
  {
    static_assert(Min >= 1 &&
                  Max >= 1 &&
                  Max <= 6 &&
                  Min <= Max);

    using QValidator::QValidator;

    State
    validate(QString & input,
             int     & pos) const override
    {
      // Ensure the input is upper case and get the size.

      input           = input.toUpper();
      auto const size = input.size();

      // If nothing's been entered, we need more from them; if over
      // the maximum, less.

      if (size == 0)      return Intermediate;
      if (size > Max * 2) return Invalid;

      // If anything up to the cursor is invalid, then we're invalid.
      // Anything after the cursor, we're willing to be hopeful about.

      if (auto const index  = invalidIndex(input);
                     index != size)
      {
        return index < pos
             ? Invalid
             : Intermediate;
      }

      // Entire input was valid. If the count is odd, or we haven't yet
      // hit the minimum, we need more from them, otherwise, we're good.

      return ((size & 1) || (size < Min * 2))
           ? Intermediate
           : Acceptable;
    }
  };

  // Convenience definitions:
  //
  //   Standard: User must specify field and square, and can optionally
  //             specify subsquare. Ideal for QSO logging.
  //
  //   Extended: User must specify field and square, and can optionally
  //             specify subsquare, extended, ultra extended, and hyper
  //             extended. Ideal for station grid entry.

  using StandardValidator = Validator<2,3>;
  using ExtendedValidator = Validator<2,6>;
}

#endif