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