OpenTTD Source 20250218-master-g53dd1258a7
string_func.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
10#include "../stdafx.h"
11
12#include "../3rdparty/catch2/catch.hpp"
13
14#include "../string_func.h"
15#include "../table/control_codes.h"
16
17/**** String compare/equals *****/
18
19TEST_CASE("StrCompareIgnoreCase - std::string")
20{
21 /* Same string, with different cases. */
22 CHECK(StrCompareIgnoreCase(std::string{""}, std::string{""}) == 0);
23 CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"a"}) == 0);
24 CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"A"}) == 0);
25 CHECK(StrCompareIgnoreCase(std::string{"A"}, std::string{"a"}) == 0);
26 CHECK(StrCompareIgnoreCase(std::string{"A"}, std::string{"A"}) == 0);
27
28 /* Not the same string. */
29 CHECK(StrCompareIgnoreCase(std::string{""}, std::string{"b"}) < 0);
30 CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{""}) > 0);
31
32 CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"b"}) < 0);
33 CHECK(StrCompareIgnoreCase(std::string{"b"}, std::string{"a"}) > 0);
34 CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"B"}) < 0);
35 CHECK(StrCompareIgnoreCase(std::string{"b"}, std::string{"A"}) > 0);
36 CHECK(StrCompareIgnoreCase(std::string{"A"}, std::string{"b"}) < 0);
37 CHECK(StrCompareIgnoreCase(std::string{"B"}, std::string{"a"}) > 0);
38
39 CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"aa"}) < 0);
40 CHECK(StrCompareIgnoreCase(std::string{"aa"}, std::string{"a"}) > 0);
41}
42
43TEST_CASE("StrCompareIgnoreCase - char pointer")
44{
45 /* Same string, with different cases. */
46 CHECK(StrCompareIgnoreCase("", "") == 0);
47 CHECK(StrCompareIgnoreCase("a", "a") == 0);
48 CHECK(StrCompareIgnoreCase("a", "A") == 0);
49 CHECK(StrCompareIgnoreCase("A", "a") == 0);
50 CHECK(StrCompareIgnoreCase("A", "A") == 0);
51
52 /* Not the same string. */
53 CHECK(StrCompareIgnoreCase("", "b") < 0);
54 CHECK(StrCompareIgnoreCase("a", "") > 0);
55
56 CHECK(StrCompareIgnoreCase("a", "b") < 0);
57 CHECK(StrCompareIgnoreCase("b", "a") > 0);
58 CHECK(StrCompareIgnoreCase("a", "B") < 0);
59 CHECK(StrCompareIgnoreCase("b", "A") > 0);
60 CHECK(StrCompareIgnoreCase("A", "b") < 0);
61 CHECK(StrCompareIgnoreCase("B", "a") > 0);
62
63 CHECK(StrCompareIgnoreCase("a", "aa") < 0);
64 CHECK(StrCompareIgnoreCase("aa", "a") > 0);
65}
66
67TEST_CASE("StrCompareIgnoreCase - std::string_view")
68{
69 /*
70 * With std::string_view the only way to access the data is via .data(),
71 * which does not guarantee the termination that would be required by
72 * things such as stricmp/strcasecmp. So, just passing .data() into stricmp
73 * or strcasecmp would fail if it does not account for the length of the
74 * view. Thus, contrary to the string/char* tests, this uses the same base
75 * string but gets different sections to trigger these corner cases.
76 */
77 std::string_view base{"aaAbB"};
78
79 /* Same string, with different cases. */
80 CHECK(StrCompareIgnoreCase(base.substr(0, 0), base.substr(1, 0)) == 0); // Different positions
81 CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(1, 1)) == 0); // Different positions
82 CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(2, 1)) == 0);
83 CHECK(StrCompareIgnoreCase(base.substr(2, 1), base.substr(1, 1)) == 0);
84 CHECK(StrCompareIgnoreCase(base.substr(2, 1), base.substr(2, 1)) == 0);
85
86 /* Not the same string. */
87 CHECK(StrCompareIgnoreCase(base.substr(3, 0), base.substr(3, 1)) < 0); // Same position, different lengths
88 CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(0, 0)) > 0); // Same position, different lengths
89
90 CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(3, 1)) < 0);
91 CHECK(StrCompareIgnoreCase(base.substr(3, 1), base.substr(0, 1)) > 0);
92 CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(4, 1)) < 0);
93 CHECK(StrCompareIgnoreCase(base.substr(3, 1), base.substr(2, 1)) > 0);
94 CHECK(StrCompareIgnoreCase(base.substr(2, 1), base.substr(3, 1)) < 0);
95 CHECK(StrCompareIgnoreCase(base.substr(4, 1), base.substr(0, 1)) > 0);
96
97 CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(0, 2)) < 0); // Same position, different lengths
98 CHECK(StrCompareIgnoreCase(base.substr(0, 2), base.substr(0, 1)) > 0); // Same position, different lengths
99}
100
101TEST_CASE("StrEqualsIgnoreCase - std::string")
102{
103 /* Same string, with different cases. */
104 CHECK(StrEqualsIgnoreCase(std::string{""}, std::string{""}));
105 CHECK(StrEqualsIgnoreCase(std::string{"a"}, std::string{"a"}));
106 CHECK(StrEqualsIgnoreCase(std::string{"a"}, std::string{"A"}));
107 CHECK(StrEqualsIgnoreCase(std::string{"A"}, std::string{"a"}));
108 CHECK(StrEqualsIgnoreCase(std::string{"A"}, std::string{"A"}));
109
110 /* Not the same string. */
111 CHECK(!StrEqualsIgnoreCase(std::string{""}, std::string{"b"}));
112 CHECK(!StrEqualsIgnoreCase(std::string{"a"}, std::string{""}));
113 CHECK(!StrEqualsIgnoreCase(std::string{"a"}, std::string{"b"}));
114 CHECK(!StrEqualsIgnoreCase(std::string{"b"}, std::string{"a"}));
115 CHECK(!StrEqualsIgnoreCase(std::string{"a"}, std::string{"aa"}));
116 CHECK(!StrEqualsIgnoreCase(std::string{"aa"}, std::string{"a"}));
117}
118
119TEST_CASE("StrEqualsIgnoreCase - char pointer")
120{
121 /* Same string, with different cases. */
122 CHECK(StrEqualsIgnoreCase("", ""));
123 CHECK(StrEqualsIgnoreCase("a", "a"));
124 CHECK(StrEqualsIgnoreCase("a", "A"));
125 CHECK(StrEqualsIgnoreCase("A", "a"));
126 CHECK(StrEqualsIgnoreCase("A", "A"));
127
128 /* Not the same string. */
129 CHECK(!StrEqualsIgnoreCase("", "b"));
130 CHECK(!StrEqualsIgnoreCase("a", ""));
131 CHECK(!StrEqualsIgnoreCase("a", "b"));
132 CHECK(!StrEqualsIgnoreCase("b", "a"));
133 CHECK(!StrEqualsIgnoreCase("a", "aa"));
134 CHECK(!StrEqualsIgnoreCase("aa", "a"));
135}
136
137TEST_CASE("StrEqualsIgnoreCase - std::string_view")
138{
139 /*
140 * With std::string_view the only way to access the data is via .data(),
141 * which does not guarantee the termination that would be required by
142 * things such as stricmp/strcasecmp. So, just passing .data() into stricmp
143 * or strcasecmp would fail if it does not account for the length of the
144 * view. Thus, contrary to the string/char* tests, this uses the same base
145 * string but gets different sections to trigger these corner cases.
146 */
147 std::string_view base{"aaAb"};
148
149 /* Same string, with different cases. */
150 CHECK(StrEqualsIgnoreCase(base.substr(0, 0), base.substr(1, 0))); // Different positions
151 CHECK(StrEqualsIgnoreCase(base.substr(0, 1), base.substr(1, 1))); // Different positions
152 CHECK(StrEqualsIgnoreCase(base.substr(0, 1), base.substr(2, 1)));
153 CHECK(StrEqualsIgnoreCase(base.substr(2, 1), base.substr(1, 1)));
154 CHECK(StrEqualsIgnoreCase(base.substr(2, 1), base.substr(2, 1)));
155
156 /* Not the same string. */
157 CHECK(!StrEqualsIgnoreCase(base.substr(3, 0), base.substr(3, 1))); // Same position, different lengths
158 CHECK(!StrEqualsIgnoreCase(base.substr(0, 1), base.substr(0, 0)));
159 CHECK(!StrEqualsIgnoreCase(base.substr(0, 1), base.substr(3, 1)));
160 CHECK(!StrEqualsIgnoreCase(base.substr(3, 1), base.substr(0, 1)));
161 CHECK(!StrEqualsIgnoreCase(base.substr(0, 1), base.substr(0, 2))); // Same position, different lengths
162 CHECK(!StrEqualsIgnoreCase(base.substr(0, 2), base.substr(0, 1))); // Same position, different lengths
163}
164
165/**** String starts with *****/
166
167TEST_CASE("StrStartsWithIgnoreCase - std::string")
168{
169 /* Everything starts with an empty prefix. */
170 CHECK(StrStartsWithIgnoreCase(std::string{""}, std::string{""}));
171 CHECK(StrStartsWithIgnoreCase(std::string{"a"}, std::string{""}));
172
173 /* Equals string, ignoring case. */
174 CHECK(StrStartsWithIgnoreCase(std::string{"a"}, std::string{"a"}));
175 CHECK(StrStartsWithIgnoreCase(std::string{"a"}, std::string{"A"}));
176 CHECK(StrStartsWithIgnoreCase(std::string{"A"}, std::string{"a"}));
177 CHECK(StrStartsWithIgnoreCase(std::string{"A"}, std::string{"A"}));
178
179 /* Starts with same, ignoring case. */
180 CHECK(StrStartsWithIgnoreCase(std::string{"ab"}, std::string{"a"}));
181 CHECK(StrStartsWithIgnoreCase(std::string{"ab"}, std::string{"A"}));
182 CHECK(StrStartsWithIgnoreCase(std::string{"Ab"}, std::string{"a"}));
183 CHECK(StrStartsWithIgnoreCase(std::string{"Ab"}, std::string{"A"}));
184
185 /* Does not start the same. */
186 CHECK(!StrStartsWithIgnoreCase(std::string{""}, std::string{"b"}));
187 CHECK(!StrStartsWithIgnoreCase(std::string{"a"}, std::string{"b"}));
188 CHECK(!StrStartsWithIgnoreCase(std::string{"b"}, std::string{"a"}));
189 CHECK(!StrStartsWithIgnoreCase(std::string{"a"}, std::string{"aa"}));
190}
191
192TEST_CASE("StrStartsWithIgnoreCase - char pointer")
193{
194 /* Everything starts with an empty prefix. */
195 CHECK(StrStartsWithIgnoreCase("", ""));
196 CHECK(StrStartsWithIgnoreCase("a", ""));
197
198 /* Equals string, ignoring case. */
199 CHECK(StrStartsWithIgnoreCase("a", "a"));
200 CHECK(StrStartsWithIgnoreCase("a", "A"));
201 CHECK(StrStartsWithIgnoreCase("A", "a"));
202 CHECK(StrStartsWithIgnoreCase("A", "A"));
203
204 /* Starts with same, ignoring case. */
205 CHECK(StrStartsWithIgnoreCase("ab", "a"));
206 CHECK(StrStartsWithIgnoreCase("ab", "A"));
207 CHECK(StrStartsWithIgnoreCase("Ab", "a"));
208 CHECK(StrStartsWithIgnoreCase("Ab", "A"));
209
210 /* Does not start the same. */
211 CHECK(!StrStartsWithIgnoreCase("", "b"));
212 CHECK(!StrStartsWithIgnoreCase("a", "b"));
213 CHECK(!StrStartsWithIgnoreCase("b", "a"));
214 CHECK(!StrStartsWithIgnoreCase("a", "aa"));
215}
216
217TEST_CASE("StrStartsWithIgnoreCase - std::string_view")
218{
219 /*
220 * With std::string_view the only way to access the data is via .data(),
221 * which does not guarantee the termination that would be required by
222 * things such as stricmp/strcasecmp. So, just passing .data() into stricmp
223 * or strcasecmp would fail if it does not account for the length of the
224 * view. Thus, contrary to the string/char* tests, this uses the same base
225 * string but gets different sections to trigger these corner cases.
226 */
227 std::string_view base{"aabAb"};
228
229 /* Everything starts with an empty prefix. */
230 CHECK(StrStartsWithIgnoreCase(base.substr(0, 0), base.substr(1, 0))); // Different positions
231 CHECK(StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(0, 0)));
232
233 /* Equals string, ignoring case. */
234 CHECK(StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(1, 1))); // Different positions
235 CHECK(StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(3, 1)));
236 CHECK(StrStartsWithIgnoreCase(base.substr(3, 1), base.substr(0, 1)));
237 CHECK(StrStartsWithIgnoreCase(base.substr(3, 1), base.substr(3, 1)));
238
239 /* Starts with same, ignoring case. */
240 CHECK(StrStartsWithIgnoreCase(base.substr(1, 2), base.substr(0, 1)));
241 CHECK(StrStartsWithIgnoreCase(base.substr(1, 2), base.substr(3, 1)));
242 CHECK(StrStartsWithIgnoreCase(base.substr(3, 2), base.substr(0, 1)));
243 CHECK(StrStartsWithIgnoreCase(base.substr(3, 2), base.substr(3, 1)));
244
245 /* Does not start the same. */
246 CHECK(!StrStartsWithIgnoreCase(base.substr(2, 0), base.substr(2, 1)));
247 CHECK(!StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(2, 1)));
248 CHECK(!StrStartsWithIgnoreCase(base.substr(2, 1), base.substr(0, 1)));
249 CHECK(!StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(0, 2)));
250}
251
252/**** String ends with *****/
253
254TEST_CASE("StrEndsWithIgnoreCase - std::string")
255{
256 /* Everything ends with an empty prefix. */
257 CHECK(StrEndsWithIgnoreCase(std::string{""}, std::string{""}));
258 CHECK(StrEndsWithIgnoreCase(std::string{"a"}, std::string{""}));
259
260 /* Equals string, ignoring case. */
261 CHECK(StrEndsWithIgnoreCase(std::string{"a"}, std::string{"a"}));
262 CHECK(StrEndsWithIgnoreCase(std::string{"a"}, std::string{"A"}));
263 CHECK(StrEndsWithIgnoreCase(std::string{"A"}, std::string{"a"}));
264 CHECK(StrEndsWithIgnoreCase(std::string{"A"}, std::string{"A"}));
265
266 /* Ends with same, ignoring case. */
267 CHECK(StrEndsWithIgnoreCase(std::string{"ba"}, std::string{"a"}));
268 CHECK(StrEndsWithIgnoreCase(std::string{"ba"}, std::string{"A"}));
269 CHECK(StrEndsWithIgnoreCase(std::string{"bA"}, std::string{"a"}));
270 CHECK(StrEndsWithIgnoreCase(std::string{"bA"}, std::string{"A"}));
271
272 /* Does not end the same. */
273 CHECK(!StrEndsWithIgnoreCase(std::string{""}, std::string{"b"}));
274 CHECK(!StrEndsWithIgnoreCase(std::string{"a"}, std::string{"b"}));
275 CHECK(!StrEndsWithIgnoreCase(std::string{"b"}, std::string{"a"}));
276 CHECK(!StrEndsWithIgnoreCase(std::string{"a"}, std::string{"aa"}));
277}
278
279TEST_CASE("StrEndsWithIgnoreCase - char pointer")
280{
281 /* Everything ends with an empty prefix. */
282 CHECK(StrEndsWithIgnoreCase("", ""));
283 CHECK(StrEndsWithIgnoreCase("a", ""));
284
285 /* Equals string, ignoring case. */
286 CHECK(StrEndsWithIgnoreCase("a", "a"));
287 CHECK(StrEndsWithIgnoreCase("a", "A"));
288 CHECK(StrEndsWithIgnoreCase("A", "a"));
289 CHECK(StrEndsWithIgnoreCase("A", "A"));
290
291 /* Ends with same, ignoring case. */
292 CHECK(StrEndsWithIgnoreCase("ba", "a"));
293 CHECK(StrEndsWithIgnoreCase("ba", "A"));
294 CHECK(StrEndsWithIgnoreCase("bA", "a"));
295 CHECK(StrEndsWithIgnoreCase("bA", "A"));
296
297 /* Does not end the same. */
298 CHECK(!StrEndsWithIgnoreCase("", "b"));
299 CHECK(!StrEndsWithIgnoreCase("a", "b"));
300 CHECK(!StrEndsWithIgnoreCase("b", "a"));
301 CHECK(!StrEndsWithIgnoreCase("a", "aa"));
302}
303
304TEST_CASE("StrEndsWithIgnoreCase - std::string_view")
305{
306 /*
307 * With std::string_view the only way to access the data is via .data(),
308 * which does not guarantee the termination that would be required by
309 * things such as stricmp/strcasecmp. So, just passing .data() into stricmp
310 * or strcasecmp would fail if it does not account for the length of the
311 * view. Thus, contrary to the string/char* tests, this uses the same base
312 * string but gets different sections to trigger these corner cases.
313 */
314 std::string_view base{"aabAba"};
315
316 /* Everything ends with an empty prefix. */
317 CHECK(StrEndsWithIgnoreCase(base.substr(0, 0), base.substr(1, 0))); // Different positions
318 CHECK(StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(0, 0)));
319
320 /* Equals string, ignoring case. */
321 CHECK(StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(1, 1))); // Different positions
322 CHECK(StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(3, 1)));
323 CHECK(StrEndsWithIgnoreCase(base.substr(3, 1), base.substr(0, 1)));
324 CHECK(StrEndsWithIgnoreCase(base.substr(3, 1), base.substr(3, 1)));
325
326 /* Ends with same, ignoring case. */
327 CHECK(StrEndsWithIgnoreCase(base.substr(2, 2), base.substr(0, 1)));
328 CHECK(StrEndsWithIgnoreCase(base.substr(2, 2), base.substr(3, 1)));
329 CHECK(StrEndsWithIgnoreCase(base.substr(4, 2), base.substr(0, 1)));
330 CHECK(StrEndsWithIgnoreCase(base.substr(4, 2), base.substr(3, 1)));
331
332 /* Does not end the same. */
333 CHECK(!StrEndsWithIgnoreCase(base.substr(2, 0), base.substr(2, 1)));
334 CHECK(!StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(2, 1)));
335 CHECK(!StrEndsWithIgnoreCase(base.substr(2, 1), base.substr(0, 1)));
336 CHECK(!StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(0, 2)));
337}
338
339
340TEST_CASE("FormatArrayAsHex")
341{
342 CHECK(FormatArrayAsHex(std::array<uint8_t, 0>{}) == "");
343 CHECK(FormatArrayAsHex(std::array<uint8_t, 1>{0x12}) == "12");
344 CHECK(FormatArrayAsHex(std::array<uint8_t, 4>{0x13, 0x38, 0x42, 0xAF}) == "133842AF");
345}
346
347TEST_CASE("ConvertHexToBytes")
348{
349 CHECK(ConvertHexToBytes("", {}) == true);
350 CHECK(ConvertHexToBytes("1", {}) == false);
351 CHECK(ConvertHexToBytes("12", {}) == false);
352
353 std::array<uint8_t, 1> bytes1;
354 CHECK(ConvertHexToBytes("1", bytes1) == false);
355 CHECK(ConvertHexToBytes("12", bytes1) == true);
356 CHECK(bytes1[0] == 0x12);
357 CHECK(ConvertHexToBytes("123", bytes1) == false);
358 CHECK(ConvertHexToBytes("1g", bytes1) == false);
359 CHECK(ConvertHexToBytes("g1", bytes1) == false);
360
361 std::array<uint8_t, 2> bytes2;
362 CHECK(ConvertHexToBytes("12", bytes2) == false);
363 CHECK(ConvertHexToBytes("1234", bytes2) == true);
364 CHECK(bytes2[0] == 0x12);
365 CHECK(bytes2[1] == 0x34);
366
367 std::array<uint8_t, 8> bytes3;
368 CHECK(ConvertHexToBytes("123456789abcdef0", bytes3) == true);
369 CHECK(bytes3[0] == 0x12);
370 CHECK(bytes3[1] == 0x34);
371 CHECK(bytes3[2] == 0x56);
372 CHECK(bytes3[3] == 0x78);
373 CHECK(bytes3[4] == 0x9a);
374 CHECK(bytes3[5] == 0xbc);
375 CHECK(bytes3[6] == 0xde);
376 CHECK(bytes3[7] == 0xf0);
377
378 CHECK(ConvertHexToBytes("123456789ABCDEF0", bytes3) == true);
379 CHECK(bytes3[0] == 0x12);
380 CHECK(bytes3[1] == 0x34);
381 CHECK(bytes3[2] == 0x56);
382 CHECK(bytes3[3] == 0x78);
383 CHECK(bytes3[4] == 0x9a);
384 CHECK(bytes3[5] == 0xbc);
385 CHECK(bytes3[6] == 0xde);
386 CHECK(bytes3[7] == 0xf0);
387}
388
389static const std::vector<std::pair<std::string, std::string>> _str_trim_testcases = {
390 {"a", "a"},
391 {" a", "a"},
392 {"a ", "a"},
393 {" a ", "a"},
394 {" a b c ", "a b c"},
395 {" ", ""}
396};
397
398TEST_CASE("StrTrimInPlace")
399{
400 for (auto [input, expected] : _str_trim_testcases) {
401 StrTrimInPlace(input);
402 CHECK(input == expected);
403 }
404}
405
406TEST_CASE("StrTrimView") {
407 for (const auto& [input, expected] : _str_trim_testcases) {
408 CHECK(StrTrimView(input) == expected);
409 }
410}
411
412extern void FixSCCEncoded(std::string &str, bool fix_code);
413
414/* Helper to call FixSCCEncoded and return the result in a new string. */
415static std::string FixSCCEncodedWrapper(const std::string &str, bool fix_code)
416{
417 std::string result = str;
418 FixSCCEncoded(result, fix_code);
419 return result;
420}
421
422/* Helper to compose a string part from a unicode character */
423static void ComposePart(std::back_insert_iterator<std::string> &output, char32_t c)
424{
425 Utf8Encode(output, c);
426}
427
428/* Helper to compose a string part from a string. */
429static void ComposePart(std::back_insert_iterator<std::string> &output, const std::string &value)
430{
431 for (const auto &c : value) *output = c;
432}
433
434/* Helper to compose a string from unicode or string parts. */
435template <typename... Args>
436static std::string Compose(Args &&... args)
437{
438 std::string result;
439 auto output = std::back_inserter(result);
440 (ComposePart(output, args), ...);
441 return result;
442}
443
444TEST_CASE("FixSCCEncoded")
445{
446 /* Test conversion of empty string. */
447 CHECK(FixSCCEncodedWrapper("", false) == "");
448
449 /* Test conversion of old code to new code. */
450 CHECK(FixSCCEncodedWrapper("\uE0280", true) == Compose(SCC_ENCODED, "0"));
451
452 /* Test conversion of two old codes to new codes. */
453 CHECK(FixSCCEncodedWrapper("\uE0280:\uE0281", true) == Compose(SCC_ENCODED, "0", SCC_RECORD_SEPARATOR, SCC_ENCODED, "1"));
454
455 /* Test conversion with no parameter. */
456 CHECK(FixSCCEncodedWrapper("\uE0001", false) == Compose(SCC_ENCODED, "1"));
457
458 /* Test conversion with one numeric parameter. */
459 CHECK(FixSCCEncodedWrapper("\uE00022:1", false) == Compose(SCC_ENCODED, "22", SCC_RECORD_SEPARATOR, SCC_ENCODED_NUMERIC, "1"));
460
461 /* Test conversion with two numeric parameters. */
462 CHECK(FixSCCEncodedWrapper("\uE0003:12:2", false) == Compose(SCC_ENCODED, "3", SCC_RECORD_SEPARATOR, SCC_ENCODED_NUMERIC, "12", SCC_RECORD_SEPARATOR, SCC_ENCODED_NUMERIC, "2"));
463
464 /* Test conversion with one string parameter. */
465 CHECK(FixSCCEncodedWrapper("\uE0004:\"Foo\"", false) == Compose(SCC_ENCODED, "4", SCC_RECORD_SEPARATOR, SCC_ENCODED_STRING, "Foo"));
466
467 /* Test conversion with two string parameters. */
468 CHECK(FixSCCEncodedWrapper("\uE00055:\"Foo\":\"Bar\"", false) == Compose(SCC_ENCODED, "55", SCC_RECORD_SEPARATOR, SCC_ENCODED_STRING, "Foo", SCC_RECORD_SEPARATOR, SCC_ENCODED_STRING, "Bar"));
469
470 /* Test conversion with two string parameters surrounding a numeric parameter. */
471 CHECK(FixSCCEncodedWrapper("\uE0006:\"Foo\":7CA:\"Bar\"", false) == Compose(SCC_ENCODED, "6", SCC_RECORD_SEPARATOR, SCC_ENCODED_STRING, "Foo", SCC_RECORD_SEPARATOR, SCC_ENCODED_NUMERIC, "7CA", SCC_RECORD_SEPARATOR, SCC_ENCODED_STRING, "Bar"));
472
473 /* Test conversion with one sub-string and two string parameters. */
474 CHECK(FixSCCEncodedWrapper("\uE000777:\uE0008888:\"Foo\":\"BarBaz\"", false) == Compose(SCC_ENCODED, "777", SCC_RECORD_SEPARATOR, SCC_ENCODED, "8888", SCC_RECORD_SEPARATOR, SCC_ENCODED_STRING, "Foo", SCC_RECORD_SEPARATOR, SCC_ENCODED_STRING, "BarBaz"));
475}
@ SCC_ENCODED
Encoded string marker and sub-string parameter.
@ SCC_ENCODED_NUMERIC
Encoded numeric parameter.
@ SCC_ENCODED_STRING
Encoded string parameter.
bool ConvertHexToBytes(std::string_view hex, std::span< uint8_t > bytes)
Convert a hex-string to a byte-array, while validating it was actually hex.
Definition string.cpp:753
bool StrEqualsIgnoreCase(const std::string_view str1, const std::string_view str2)
Compares two string( view)s for equality, while ignoring the case of the characters.
Definition string.cpp:366
std::string FormatArrayAsHex(std::span< const uint8_t > data)
Format a byte array into a continuous hex string.
Definition string.cpp:81
bool StrStartsWithIgnoreCase(std::string_view str, const std::string_view prefix)
Check whether the given string starts with the given prefix, ignoring case.
Definition string.cpp:300
int StrCompareIgnoreCase(const std::string_view str1, const std::string_view str2)
Compares two string( view)s, while ignoring the case of the characters.
Definition string.cpp:353
bool StrEndsWithIgnoreCase(std::string_view str, const std::string_view suffix)
Check whether the given string ends with the given suffix, ignoring case.
Definition string.cpp:340
void StrTrimInPlace(std::string &str)
Trim the spaces from given string in place, i.e.
Definition string.cpp:279
size_t Utf8Encode(T buf, char32_t c)
Encode a unicode character and place it in the buffer.
Definition string.cpp:479
void FixSCCEncoded(std::string &str, bool fix_code)
Scan the string for old values of SCC_ENCODED and fix it to it's new, value.
Definition saveload.cpp:919