MythTV master
stringutil.cpp
Go to the documentation of this file.
1#include <array>
2#include <bit>
3#include <QObject>
4#include "stringutil.h"
5
6// For the spaceship operator, the c++ standard library explicitly
7// requires '0' and not nullptr. NOLINTBEGIN(modernize-use-nullptr)
8
9bool StringUtil::isValidUTF8(const QByteArray& data)
10{
11 const auto* p = (const unsigned char*)data.data();
12 const unsigned char* const end = p + data.size();
13 while (p < end)
14 {
15 int code_point_length = std::countl_one(*p);
16
17 switch (code_point_length)
18 {
19 case 0: p++; continue; // ASCII
20 case 1: return false; // invalid, continuation byte
21 case 2:
22 case 3:
23 case 4: break;
24 default: return false;
25 /* the variable length code is limited to 4 bytes by RFC3629 §3 to match
26 the range of UTF-16, i.e. the maximum code point is U+10FFFF
27 */
28 }
29
30 if (end < code_point_length + p)
31 {
32 return false; // truncated codepoint at end
33 }
34
35 // verify each starting byte is followed by the correct number of continuation bytes
36 switch (code_point_length)
37 {
38 case 4:
39 if (std::countl_one(p[3]) != 1)
40 {
41 return false;
42 }
43 [[fallthrough]];
44 case 3:
45 if (std::countl_one(p[2]) != 1)
46 {
47 return false;
48 }
49 [[fallthrough]];
50 case 2:
51 if (std::countl_one(p[1]) != 1)
52 {
53 return false;
54 }
55 break;
56 default: break; // should never be reached
57 }
58
59 // all continuation bytes are in the range 0x80 to 0xBF
60 switch (code_point_length)
61 {
62 case 2:
63 // overlong encoding of single byte character
64 if (*p == 0xC0 || *p == 0xC1)
65 {
66 return false;
67 }
68 break;
69 case 3:
70 // U+D800–U+DFFF are invalid; UTF-16 surrogate halves
71 // 0xED'A0'80 to 0xED'BF'BF
72 if (p[0] == 0xED && p[1] >= 0xA0)
73 {
74 return false;
75 }
76 // overlong encoding of 2 byte character
77 if (p[0] == 0xE0 && p[1] < 0xA0)
78 {
79 return false;
80 }
81 break;
82 case 4:
83 // code points > U+10FFFF are invalid
84 // U+10FFFF in UTF-8 is 0xF4'8F'BF'BF
85 // U+110000 in UTF-8 is 0xF4'90'80'80
86 if (*p > 0xF4 || (p[0] == 0xF4 && p[1] >= 0x90))
87 {
88 return false;
89 }
90 // overlong encoding of 3 byte character
91 if (p[0] == 0xF0 && p[1] < 0x90)
92 {
93 return false;
94 }
95 break;
96 default: break; // should never be reached
97 }
98
99 p += code_point_length;
100 }
101
102 return true;
103}
104
122std::strong_ordering StringUtil::naturalCompare(const QString &_a, const QString &_b, Qt::CaseSensitivity caseSensitivity)
123{
124 QString a;
125 QString b;
126
127 if (caseSensitivity == Qt::CaseSensitive)
128 {
129 a = _a;
130 b = _b;
131 }
132 else
133 {
134 a = _a.toLower();
135 b = _b.toLower();
136 }
137
138 const QChar* currA = a.unicode(); // iterator over a
139 const QChar* currB = b.unicode(); // iterator over b
140
141 if (currA == currB)
142 {
143 return std::strong_ordering::equal;
144 }
145
146 while (!currA->isNull() && !currB->isNull())
147 {
148 const QChar* begSeqA = currA; // beginning of a new character sequence of a
149 const QChar* begSeqB = currB;
150
151 if (currA->unicode() == QChar::ObjectReplacementCharacter)
152 return std::strong_ordering::greater;
153 if (currB->unicode() == QChar::ObjectReplacementCharacter)
154 return std::strong_ordering::less;
155
156 if (currA->unicode() == QChar::ReplacementCharacter)
157 return std::strong_ordering::greater;
158 if (currB->unicode() == QChar::ReplacementCharacter)
159 return std::strong_ordering::less;
160
161 // find sequence of characters ending at the first non-character
162 while (!currA->isNull() && !currA->isDigit() && !currA->isPunct() &&
163 !currA->isSpace())
164 {
165 ++currA;
166 }
167
168 while (!currB->isNull() && !currB->isDigit() && !currB->isPunct() &&
169 !currB->isSpace())
170 {
171 ++currB;
172 }
173
174 // compare these sequences
175 const QString& subA(a.mid(begSeqA - a.unicode(), currA - begSeqA));
176 const QString& subB(b.mid(begSeqB - b.unicode(), currB - begSeqB));
177 int cmp = QString::localeAwareCompare(subA, subB);
178 if (cmp != 0)
179 return cmp < 0 ? std::strong_ordering::less : std::strong_ordering::greater;
180
181 if (currA->isNull() || currB->isNull())
182 break;
183
184 // find sequence of characters ending at the first non-character
185 while ((currA->isPunct() || currA->isSpace()) &&
186 (currB->isPunct() || currB->isSpace()))
187 {
188 auto cmp2 = (currA->unicode() <=> currB->unicode());
189 if (cmp2 != 0)
190 return cmp2;
191 ++currA;
192 ++currB;
193 if (currA->isNull() || currB->isNull())
194 {
195 break;
196 }
197 }
198
199 // now some digits follow...
200 if ((*currA == QLatin1Char('0')) || (*currB == QLatin1Char('0')))
201 {
202 // one digit-sequence starts with 0 -> assume we are in a fraction part
203 // do left aligned comparison (numbers are considered left aligned)
204 while (true)
205 {
206 if (!currA->isDigit() && !currB->isDigit())
207 break;
208 if (!currA->isDigit())
209 return std::strong_ordering::greater;
210 if (!currB->isDigit())
211 return std::strong_ordering::less;
212 auto cmp2 = (currA->unicode() <=> currB->unicode());
213 if (cmp2 != 0)
214 return cmp2;
215 ++currA;
216 ++currB;
217 }
218 }
219 else
220 {
221 // No digit-sequence starts with 0 -> assume we are looking at some integer
222 // do right aligned comparison.
223 //
224 // The longest run of digits wins. That aside, the greatest
225 // value wins, but we can't know that it will until we've scanned
226 // both numbers to know that they have the same magnitude.
227
228 bool isFirstRun = true;
229 std::strong_ordering weight = std::strong_ordering::equal;
230
231 while (true)
232 {
233 if (!currA->isDigit() && !currB->isDigit())
234 {
235 if (weight != 0)
236 return weight;
237 break;
238 }
239 if (!currA->isDigit())
240 {
241 if (isFirstRun)
242 return currA->unicode() <=> currB->unicode();
243 return std::strong_ordering::less;
244 }
245 if (!currB->isDigit())
246 {
247 if (isFirstRun)
248 return currA->unicode() <=> currB->unicode();
249 return std::strong_ordering::greater;
250 }
251 if (weight == 0)
252 weight = (currA->unicode() <=> currB->unicode());
253 ++currA;
254 ++currB;
255 isFirstRun = false;
256 }
257 }
258 }
259
260 if (currA->isNull() && currB->isNull())
261 {
262 if (caseSensitivity == Qt::CaseSensitive)
263 return std::strong_ordering::equal;
264 return std::strong_ordering::equivalent;
265 }
266
267 return currA->isNull() ? std::strong_ordering::less : std::strong_ordering::greater;
268}
269
270static constexpr int64_t kOneTerabyte { 1024 * 1024LL * 1024};
271static constexpr int64_t kOneGigabyte { 1024LL * 1024};
272static constexpr int64_t kOneMegabyte { 1024};
273
281QString StringUtil::formatKBytes(int64_t sizeKB, int precision)
282{
283 if (sizeKB > kOneTerabyte)
284 {
285 double sizeTB = sizeKB/(1.0 * kOneTerabyte);
286 return QObject::tr("%1 TB").arg(sizeTB, 0, 'f', (sizeTB>10)?0:precision);
287 }
288 if (sizeKB > kOneGigabyte)
289 {
290 double sizeGB = sizeKB/(1.0 * kOneGigabyte);
291 return QObject::tr("%1 GB").arg(sizeGB, 0, 'f', (sizeGB>10)?0:precision);
292 }
293 if (sizeKB > kOneMegabyte)
294 {
295 double sizeMB = sizeKB/(1.0 * kOneMegabyte);
296 return QObject::tr("%1 MB").arg(sizeMB, 0, 'f', (sizeMB>10)?0:precision);
297 }
298 // Kilobytes
299 return QObject::tr("%1 KB").arg(sizeKB);
300}
301
302QString StringUtil::formatBytes(int64_t sizeB, int precision)
303{
304 if (sizeB > 1024)
305 return formatKBytes(sizeB / 1024, precision);
306 return QString("%1 B").arg(sizeB);
307}
308
309// NOLINTEND(modernize-use-nullptr)
MBASE_PUBLIC bool isValidUTF8(const QByteArray &data)
Definition: stringutil.cpp:9
MBASE_PUBLIC std::strong_ordering naturalCompare(const QString &_a, const QString &_b, Qt::CaseSensitivity caseSensitivity=Qt::CaseSensitive)
This method chops the input a and b into pieces of digits and non-digits (a1.05 becomes a | 1 | .
Definition: stringutil.cpp:122
MBASE_PUBLIC QString formatBytes(int64_t sizeB, int prec=1)
Definition: stringutil.cpp:302
MBASE_PUBLIC QString formatKBytes(int64_t sizeKB, int prec=1)
Definition: stringutil.cpp:281
static constexpr int64_t kOneTerabyte
Definition: stringutil.cpp:270
static constexpr int64_t kOneGigabyte
Definition: stringutil.cpp:271
static constexpr int64_t kOneMegabyte
Definition: stringutil.cpp:272