OpenTTD Source  20241111-master-gce64d5f5d9
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 
16 /**** String compare/equals *****/
17 
18 TEST_CASE("StrCompareIgnoreCase - std::string")
19 {
20  /* Same string, with different cases. */
21  CHECK(StrCompareIgnoreCase(std::string{""}, std::string{""}) == 0);
22  CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"a"}) == 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 
27  /* Not the same string. */
28  CHECK(StrCompareIgnoreCase(std::string{""}, std::string{"b"}) < 0);
29  CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{""}) > 0);
30 
31  CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"b"}) < 0);
32  CHECK(StrCompareIgnoreCase(std::string{"b"}, std::string{"a"}) > 0);
33  CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"B"}) < 0);
34  CHECK(StrCompareIgnoreCase(std::string{"b"}, std::string{"A"}) > 0);
35  CHECK(StrCompareIgnoreCase(std::string{"A"}, std::string{"b"}) < 0);
36  CHECK(StrCompareIgnoreCase(std::string{"B"}, std::string{"a"}) > 0);
37 
38  CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"aa"}) < 0);
39  CHECK(StrCompareIgnoreCase(std::string{"aa"}, std::string{"a"}) > 0);
40 }
41 
42 TEST_CASE("StrCompareIgnoreCase - char pointer")
43 {
44  /* Same string, with different cases. */
45  CHECK(StrCompareIgnoreCase("", "") == 0);
46  CHECK(StrCompareIgnoreCase("a", "a") == 0);
47  CHECK(StrCompareIgnoreCase("a", "A") == 0);
48  CHECK(StrCompareIgnoreCase("A", "a") == 0);
49  CHECK(StrCompareIgnoreCase("A", "A") == 0);
50 
51  /* Not the same string. */
52  CHECK(StrCompareIgnoreCase("", "b") < 0);
53  CHECK(StrCompareIgnoreCase("a", "") > 0);
54 
55  CHECK(StrCompareIgnoreCase("a", "b") < 0);
56  CHECK(StrCompareIgnoreCase("b", "a") > 0);
57  CHECK(StrCompareIgnoreCase("a", "B") < 0);
58  CHECK(StrCompareIgnoreCase("b", "A") > 0);
59  CHECK(StrCompareIgnoreCase("A", "b") < 0);
60  CHECK(StrCompareIgnoreCase("B", "a") > 0);
61 
62  CHECK(StrCompareIgnoreCase("a", "aa") < 0);
63  CHECK(StrCompareIgnoreCase("aa", "a") > 0);
64 }
65 
66 TEST_CASE("StrCompareIgnoreCase - std::string_view")
67 {
68  /*
69  * With std::string_view the only way to access the data is via .data(),
70  * which does not guarantee the termination that would be required by
71  * things such as stricmp/strcasecmp. So, just passing .data() into stricmp
72  * or strcasecmp would fail if it does not account for the length of the
73  * view. Thus, contrary to the string/char* tests, this uses the same base
74  * string but gets different sections to trigger these corner cases.
75  */
76  std::string_view base{"aaAbB"};
77 
78  /* Same string, with different cases. */
79  CHECK(StrCompareIgnoreCase(base.substr(0, 0), base.substr(1, 0)) == 0); // Different positions
80  CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(1, 1)) == 0); // Different positions
81  CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(2, 1)) == 0);
82  CHECK(StrCompareIgnoreCase(base.substr(2, 1), base.substr(1, 1)) == 0);
83  CHECK(StrCompareIgnoreCase(base.substr(2, 1), base.substr(2, 1)) == 0);
84 
85  /* Not the same string. */
86  CHECK(StrCompareIgnoreCase(base.substr(3, 0), base.substr(3, 1)) < 0); // Same position, different lengths
87  CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(0, 0)) > 0); // Same position, different lengths
88 
89  CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(3, 1)) < 0);
90  CHECK(StrCompareIgnoreCase(base.substr(3, 1), base.substr(0, 1)) > 0);
91  CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(4, 1)) < 0);
92  CHECK(StrCompareIgnoreCase(base.substr(3, 1), base.substr(2, 1)) > 0);
93  CHECK(StrCompareIgnoreCase(base.substr(2, 1), base.substr(3, 1)) < 0);
94  CHECK(StrCompareIgnoreCase(base.substr(4, 1), base.substr(0, 1)) > 0);
95 
96  CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(0, 2)) < 0); // Same position, different lengths
97  CHECK(StrCompareIgnoreCase(base.substr(0, 2), base.substr(0, 1)) > 0); // Same position, different lengths
98 }
99 
100 TEST_CASE("StrEqualsIgnoreCase - std::string")
101 {
102  /* Same string, with different cases. */
103  CHECK(StrEqualsIgnoreCase(std::string{""}, std::string{""}));
104  CHECK(StrEqualsIgnoreCase(std::string{"a"}, std::string{"a"}));
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 
109  /* Not the same string. */
110  CHECK(!StrEqualsIgnoreCase(std::string{""}, std::string{"b"}));
111  CHECK(!StrEqualsIgnoreCase(std::string{"a"}, std::string{""}));
112  CHECK(!StrEqualsIgnoreCase(std::string{"a"}, std::string{"b"}));
113  CHECK(!StrEqualsIgnoreCase(std::string{"b"}, std::string{"a"}));
114  CHECK(!StrEqualsIgnoreCase(std::string{"a"}, std::string{"aa"}));
115  CHECK(!StrEqualsIgnoreCase(std::string{"aa"}, std::string{"a"}));
116 }
117 
118 TEST_CASE("StrEqualsIgnoreCase - char pointer")
119 {
120  /* Same string, with different cases. */
121  CHECK(StrEqualsIgnoreCase("", ""));
122  CHECK(StrEqualsIgnoreCase("a", "a"));
123  CHECK(StrEqualsIgnoreCase("a", "A"));
124  CHECK(StrEqualsIgnoreCase("A", "a"));
125  CHECK(StrEqualsIgnoreCase("A", "A"));
126 
127  /* Not the same string. */
128  CHECK(!StrEqualsIgnoreCase("", "b"));
129  CHECK(!StrEqualsIgnoreCase("a", ""));
130  CHECK(!StrEqualsIgnoreCase("a", "b"));
131  CHECK(!StrEqualsIgnoreCase("b", "a"));
132  CHECK(!StrEqualsIgnoreCase("a", "aa"));
133  CHECK(!StrEqualsIgnoreCase("aa", "a"));
134 }
135 
136 TEST_CASE("StrEqualsIgnoreCase - std::string_view")
137 {
138  /*
139  * With std::string_view the only way to access the data is via .data(),
140  * which does not guarantee the termination that would be required by
141  * things such as stricmp/strcasecmp. So, just passing .data() into stricmp
142  * or strcasecmp would fail if it does not account for the length of the
143  * view. Thus, contrary to the string/char* tests, this uses the same base
144  * string but gets different sections to trigger these corner cases.
145  */
146  std::string_view base{"aaAb"};
147 
148  /* Same string, with different cases. */
149  CHECK(StrEqualsIgnoreCase(base.substr(0, 0), base.substr(1, 0))); // Different positions
150  CHECK(StrEqualsIgnoreCase(base.substr(0, 1), base.substr(1, 1))); // Different positions
151  CHECK(StrEqualsIgnoreCase(base.substr(0, 1), base.substr(2, 1)));
152  CHECK(StrEqualsIgnoreCase(base.substr(2, 1), base.substr(1, 1)));
153  CHECK(StrEqualsIgnoreCase(base.substr(2, 1), base.substr(2, 1)));
154 
155  /* Not the same string. */
156  CHECK(!StrEqualsIgnoreCase(base.substr(3, 0), base.substr(3, 1))); // Same position, different lengths
157  CHECK(!StrEqualsIgnoreCase(base.substr(0, 1), base.substr(0, 0)));
158  CHECK(!StrEqualsIgnoreCase(base.substr(0, 1), base.substr(3, 1)));
159  CHECK(!StrEqualsIgnoreCase(base.substr(3, 1), base.substr(0, 1)));
160  CHECK(!StrEqualsIgnoreCase(base.substr(0, 1), base.substr(0, 2))); // Same position, different lengths
161  CHECK(!StrEqualsIgnoreCase(base.substr(0, 2), base.substr(0, 1))); // Same position, different lengths
162 }
163 
164 /**** String starts with *****/
165 
166 TEST_CASE("StrStartsWithIgnoreCase - std::string")
167 {
168  /* Everything starts with an empty prefix. */
169  CHECK(StrStartsWithIgnoreCase(std::string{""}, std::string{""}));
170  CHECK(StrStartsWithIgnoreCase(std::string{"a"}, std::string{""}));
171 
172  /* Equals string, ignoring case. */
173  CHECK(StrStartsWithIgnoreCase(std::string{"a"}, std::string{"a"}));
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 
178  /* Starts with same, ignoring case. */
179  CHECK(StrStartsWithIgnoreCase(std::string{"ab"}, std::string{"a"}));
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 
184  /* Does not start the same. */
185  CHECK(!StrStartsWithIgnoreCase(std::string{""}, std::string{"b"}));
186  CHECK(!StrStartsWithIgnoreCase(std::string{"a"}, std::string{"b"}));
187  CHECK(!StrStartsWithIgnoreCase(std::string{"b"}, std::string{"a"}));
188  CHECK(!StrStartsWithIgnoreCase(std::string{"a"}, std::string{"aa"}));
189 }
190 
191 TEST_CASE("StrStartsWithIgnoreCase - char pointer")
192 {
193  /* Everything starts with an empty prefix. */
194  CHECK(StrStartsWithIgnoreCase("", ""));
195  CHECK(StrStartsWithIgnoreCase("a", ""));
196 
197  /* Equals string, ignoring case. */
198  CHECK(StrStartsWithIgnoreCase("a", "a"));
199  CHECK(StrStartsWithIgnoreCase("a", "A"));
200  CHECK(StrStartsWithIgnoreCase("A", "a"));
201  CHECK(StrStartsWithIgnoreCase("A", "A"));
202 
203  /* Starts with same, ignoring case. */
204  CHECK(StrStartsWithIgnoreCase("ab", "a"));
205  CHECK(StrStartsWithIgnoreCase("ab", "A"));
206  CHECK(StrStartsWithIgnoreCase("Ab", "a"));
207  CHECK(StrStartsWithIgnoreCase("Ab", "A"));
208 
209  /* Does not start the same. */
210  CHECK(!StrStartsWithIgnoreCase("", "b"));
211  CHECK(!StrStartsWithIgnoreCase("a", "b"));
212  CHECK(!StrStartsWithIgnoreCase("b", "a"));
213  CHECK(!StrStartsWithIgnoreCase("a", "aa"));
214 }
215 
216 TEST_CASE("StrStartsWithIgnoreCase - std::string_view")
217 {
218  /*
219  * With std::string_view the only way to access the data is via .data(),
220  * which does not guarantee the termination that would be required by
221  * things such as stricmp/strcasecmp. So, just passing .data() into stricmp
222  * or strcasecmp would fail if it does not account for the length of the
223  * view. Thus, contrary to the string/char* tests, this uses the same base
224  * string but gets different sections to trigger these corner cases.
225  */
226  std::string_view base{"aabAb"};
227 
228  /* Everything starts with an empty prefix. */
229  CHECK(StrStartsWithIgnoreCase(base.substr(0, 0), base.substr(1, 0))); // Different positions
230  CHECK(StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(0, 0)));
231 
232  /* Equals string, ignoring case. */
233  CHECK(StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(1, 1))); // Different positions
234  CHECK(StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(3, 1)));
235  CHECK(StrStartsWithIgnoreCase(base.substr(3, 1), base.substr(0, 1)));
236  CHECK(StrStartsWithIgnoreCase(base.substr(3, 1), base.substr(3, 1)));
237 
238  /* Starts with same, ignoring case. */
239  CHECK(StrStartsWithIgnoreCase(base.substr(1, 2), base.substr(0, 1)));
240  CHECK(StrStartsWithIgnoreCase(base.substr(1, 2), base.substr(3, 1)));
241  CHECK(StrStartsWithIgnoreCase(base.substr(3, 2), base.substr(0, 1)));
242  CHECK(StrStartsWithIgnoreCase(base.substr(3, 2), base.substr(3, 1)));
243 
244  /* Does not start the same. */
245  CHECK(!StrStartsWithIgnoreCase(base.substr(2, 0), base.substr(2, 1)));
246  CHECK(!StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(2, 1)));
247  CHECK(!StrStartsWithIgnoreCase(base.substr(2, 1), base.substr(0, 1)));
248  CHECK(!StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(0, 2)));
249 }
250 
251 /**** String ends with *****/
252 
253 TEST_CASE("StrEndsWithIgnoreCase - std::string")
254 {
255  /* Everything ends with an empty prefix. */
256  CHECK(StrEndsWithIgnoreCase(std::string{""}, std::string{""}));
257  CHECK(StrEndsWithIgnoreCase(std::string{"a"}, std::string{""}));
258 
259  /* Equals string, ignoring case. */
260  CHECK(StrEndsWithIgnoreCase(std::string{"a"}, std::string{"a"}));
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 
265  /* Ends with same, ignoring case. */
266  CHECK(StrEndsWithIgnoreCase(std::string{"ba"}, std::string{"a"}));
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 
271  /* Does not end the same. */
272  CHECK(!StrEndsWithIgnoreCase(std::string{""}, std::string{"b"}));
273  CHECK(!StrEndsWithIgnoreCase(std::string{"a"}, std::string{"b"}));
274  CHECK(!StrEndsWithIgnoreCase(std::string{"b"}, std::string{"a"}));
275  CHECK(!StrEndsWithIgnoreCase(std::string{"a"}, std::string{"aa"}));
276 }
277 
278 TEST_CASE("StrEndsWithIgnoreCase - char pointer")
279 {
280  /* Everything ends with an empty prefix. */
281  CHECK(StrEndsWithIgnoreCase("", ""));
282  CHECK(StrEndsWithIgnoreCase("a", ""));
283 
284  /* Equals string, ignoring case. */
285  CHECK(StrEndsWithIgnoreCase("a", "a"));
286  CHECK(StrEndsWithIgnoreCase("a", "A"));
287  CHECK(StrEndsWithIgnoreCase("A", "a"));
288  CHECK(StrEndsWithIgnoreCase("A", "A"));
289 
290  /* Ends with same, ignoring case. */
291  CHECK(StrEndsWithIgnoreCase("ba", "a"));
292  CHECK(StrEndsWithIgnoreCase("ba", "A"));
293  CHECK(StrEndsWithIgnoreCase("bA", "a"));
294  CHECK(StrEndsWithIgnoreCase("bA", "A"));
295 
296  /* Does not end the same. */
297  CHECK(!StrEndsWithIgnoreCase("", "b"));
298  CHECK(!StrEndsWithIgnoreCase("a", "b"));
299  CHECK(!StrEndsWithIgnoreCase("b", "a"));
300  CHECK(!StrEndsWithIgnoreCase("a", "aa"));
301 }
302 
303 TEST_CASE("StrEndsWithIgnoreCase - std::string_view")
304 {
305  /*
306  * With std::string_view the only way to access the data is via .data(),
307  * which does not guarantee the termination that would be required by
308  * things such as stricmp/strcasecmp. So, just passing .data() into stricmp
309  * or strcasecmp would fail if it does not account for the length of the
310  * view. Thus, contrary to the string/char* tests, this uses the same base
311  * string but gets different sections to trigger these corner cases.
312  */
313  std::string_view base{"aabAba"};
314 
315  /* Everything ends with an empty prefix. */
316  CHECK(StrEndsWithIgnoreCase(base.substr(0, 0), base.substr(1, 0))); // Different positions
317  CHECK(StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(0, 0)));
318 
319  /* Equals string, ignoring case. */
320  CHECK(StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(1, 1))); // Different positions
321  CHECK(StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(3, 1)));
322  CHECK(StrEndsWithIgnoreCase(base.substr(3, 1), base.substr(0, 1)));
323  CHECK(StrEndsWithIgnoreCase(base.substr(3, 1), base.substr(3, 1)));
324 
325  /* Ends with same, ignoring case. */
326  CHECK(StrEndsWithIgnoreCase(base.substr(2, 2), base.substr(0, 1)));
327  CHECK(StrEndsWithIgnoreCase(base.substr(2, 2), base.substr(3, 1)));
328  CHECK(StrEndsWithIgnoreCase(base.substr(4, 2), base.substr(0, 1)));
329  CHECK(StrEndsWithIgnoreCase(base.substr(4, 2), base.substr(3, 1)));
330 
331  /* Does not end the same. */
332  CHECK(!StrEndsWithIgnoreCase(base.substr(2, 0), base.substr(2, 1)));
333  CHECK(!StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(2, 1)));
334  CHECK(!StrEndsWithIgnoreCase(base.substr(2, 1), base.substr(0, 1)));
335  CHECK(!StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(0, 2)));
336 }
337 
338 
339 TEST_CASE("FormatArrayAsHex")
340 {
341  CHECK(FormatArrayAsHex(std::array<uint8_t, 0>{}) == "");
342  CHECK(FormatArrayAsHex(std::array<uint8_t, 1>{0x12}) == "12");
343  CHECK(FormatArrayAsHex(std::array<uint8_t, 4>{0x13, 0x38, 0x42, 0xAF}) == "133842AF");
344 }
345 
346 TEST_CASE("ConvertHexToBytes")
347 {
348  CHECK(ConvertHexToBytes("", {}) == true);
349  CHECK(ConvertHexToBytes("1", {}) == false);
350  CHECK(ConvertHexToBytes("12", {}) == false);
351 
352  std::array<uint8_t, 1> bytes1;
353  CHECK(ConvertHexToBytes("1", bytes1) == false);
354  CHECK(ConvertHexToBytes("12", bytes1) == true);
355  CHECK(bytes1[0] == 0x12);
356  CHECK(ConvertHexToBytes("123", bytes1) == false);
357  CHECK(ConvertHexToBytes("1g", bytes1) == false);
358  CHECK(ConvertHexToBytes("g1", bytes1) == false);
359 
360  std::array<uint8_t, 2> bytes2;
361  CHECK(ConvertHexToBytes("12", bytes2) == false);
362  CHECK(ConvertHexToBytes("1234", bytes2) == true);
363  CHECK(bytes2[0] == 0x12);
364  CHECK(bytes2[1] == 0x34);
365 
366  std::array<uint8_t, 8> bytes3;
367  CHECK(ConvertHexToBytes("123456789abcdef0", bytes3) == true);
368  CHECK(bytes3[0] == 0x12);
369  CHECK(bytes3[1] == 0x34);
370  CHECK(bytes3[2] == 0x56);
371  CHECK(bytes3[3] == 0x78);
372  CHECK(bytes3[4] == 0x9a);
373  CHECK(bytes3[5] == 0xbc);
374  CHECK(bytes3[6] == 0xde);
375  CHECK(bytes3[7] == 0xf0);
376 
377  CHECK(ConvertHexToBytes("123456789ABCDEF0", bytes3) == true);
378  CHECK(bytes3[0] == 0x12);
379  CHECK(bytes3[1] == 0x34);
380  CHECK(bytes3[2] == 0x56);
381  CHECK(bytes3[3] == 0x78);
382  CHECK(bytes3[4] == 0x9a);
383  CHECK(bytes3[5] == 0xbc);
384  CHECK(bytes3[6] == 0xde);
385  CHECK(bytes3[7] == 0xf0);
386 }
387 
388 static const std::vector<std::pair<std::string, std::string>> _str_trim_testcases = {
389  {"a", "a"},
390  {" a", "a"},
391  {"a ", "a"},
392  {" a ", "a"},
393  {" a b c ", "a b c"},
394  {" ", ""}
395 };
396 
397 TEST_CASE("StrTrimInPlace")
398 {
399  for (auto [input, expected] : _str_trim_testcases) {
400  StrTrimInPlace(input);
401  CHECK(input == expected);
402  }
403 }
404 
405 TEST_CASE("StrTrimView") {
406  for (const auto& [input, expected] : _str_trim_testcases) {
407  CHECK(StrTrimView(input) == expected);
408  }
409 }
410 
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:734
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:347
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:281
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:334
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:321
void StrTrimInPlace(std::string &str)
Trim the spaces from given string in place, i.e.
Definition: string.cpp:260