Line data Source code
1 : /* Copyright (C) 2021 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
4 : * 0 A.D. is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 2 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * 0 A.D. is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "precompiled.h"
19 :
20 : #ifndef CStr_CPP_FIRST
21 : #define CStr_CPP_FIRST
22 :
23 : #include "lib/fnv_hash.h"
24 : #include "lib/utf8.h"
25 : #include "lib/byte_order.h"
26 : #include "network/Serialization.h"
27 :
28 : #include <cctype>
29 : #include <cwctype>
30 : #include <iomanip>
31 : #include <sstream>
32 : #include <type_traits>
33 :
34 : namespace
35 : {
36 : // Use a knowingly false expression, as we can't use
37 : // static_assert(false, ...) directly, because it's an ill-formed program
38 : // with a value (false) which doesn't depend on any input parameter.
39 : // We don't use constexpr bool AlwaysFalse = false, because some compilers
40 : // complain about an unused constant.
41 : template<typename T>
42 : struct AlwaysFalse : std::false_type
43 : {};
44 :
45 : template<typename StrBase>
46 : using tstringstream = std::basic_stringstream<typename StrBase::value_type>;
47 :
48 : template<typename Char>
49 102 : bool istspace(const Char chr)
50 : {
51 : if constexpr (std::is_same_v<Char, char>)
52 102 : return static_cast<bool>(std::isspace(chr));
53 : else
54 0 : return static_cast<bool>(std::iswspace(chr));
55 : }
56 :
57 : template<typename Char>
58 11500 : Char totlower(const Char chr)
59 : {
60 : if constexpr (std::is_same_v<Char, char>)
61 11500 : return std::tolower(chr);
62 : else
63 0 : return std::towlower(chr);
64 : }
65 :
66 : template<typename Char>
67 832 : Char totupper(const Char chr)
68 : {
69 : if constexpr (std::is_same_v<Char, char>)
70 832 : return std::toupper(chr);
71 : else
72 0 : return std::towupper(chr);
73 : }
74 :
75 : template<typename StrBase>
76 2 : u8* SerializeImpl(const StrBase& str, u8* buffer)
77 : {
78 : using Char = typename StrBase::value_type;
79 2 : ENSURE(buffer);
80 : if constexpr (std::is_same_v<Char, char>)
81 : {
82 : // CStr8 is always serialized to / from ASCII(or whatever 8 - bit codepage stored
83 : // in the CStr).
84 2 : size_t len = str.length();
85 2 : Serialize_int_4(buffer, (u32)len);
86 2 : size_t i = 0;
87 12 : for (i = 0; i < len; i++)
88 10 : buffer[i] = str[i];
89 2 : return buffer + len;
90 : }
91 : else if constexpr (std::is_same_v<Char, wchar_t>)
92 : {
93 : // CStrW is always serialized to / from UTF - 16.
94 0 : size_t len = str.length();
95 0 : size_t i = 0;
96 0 : for (i = 0; i < len; i++)
97 : {
98 0 : const u16 bigEndian = to_be16(str[i]);
99 0 : *(u16 *)(buffer + i * 2) = bigEndian;
100 : }
101 0 : *(u16 *)(buffer + i * 2) = 0;
102 0 : return buffer + len * 2 + 2;
103 : }
104 : else
105 : static_assert(AlwaysFalse<Char>::value, "Not implemented.");
106 : }
107 :
108 : template<typename StrBase>
109 4 : const u8* DeserializeImpl(const u8* buffer, const u8* bufferend, StrBase& str)
110 : {
111 : using Char = typename StrBase::value_type;
112 4 : ENSURE(buffer);
113 4 : ENSURE(bufferend);
114 : if constexpr (std::is_same_v<Char, char>)
115 : {
116 : u32 len;
117 4 : Deserialize_int_4(buffer, len);
118 4 : if (buffer + len > bufferend)
119 0 : return NULL;
120 4 : str = StrBase(buffer, buffer + len);
121 4 : return buffer + len;
122 : }
123 : else if constexpr (std::is_same_v<Char, wchar_t>)
124 : {
125 0 : const u16 *strend = (const u16 *)buffer;
126 0 : while ((const u8 *)strend < bufferend && *strend)
127 0 : strend++;
128 0 : if ((const u8 *)strend >= bufferend)
129 0 : return nullptr;
130 :
131 0 : str.resize(strend - (const u16 *)buffer);
132 0 : const u16 *ptr = (const u16 *)buffer;
133 :
134 0 : typename StrBase::iterator it = str.begin();
135 0 : while (ptr < strend)
136 : {
137 0 : const u16 native = to_be16(*(ptr++)); // we want from_be16, but that's the same
138 0 : *(it++) = (Char)native;
139 : }
140 :
141 0 : return (const u8 *)(strend + 1);
142 : }
143 : else
144 : static_assert(AlwaysFalse<Char>::value, "Not implemented.");
145 : }
146 :
147 : template<typename StrBase>
148 2 : size_t GetSerializedLengthImpl(const StrBase& str)
149 : {
150 : using Char = typename StrBase::value_type;
151 : if constexpr (std::is_same_v<Char, char>)
152 2 : return str.length() + 4;
153 : else if constexpr (std::is_same_v<Char, wchar_t>)
154 0 : return str.length() * 2 + 2;
155 : else
156 : static_assert(AlwaysFalse<Char>::value, "Not implemented.");
157 : }
158 :
159 : } // anonymous namespace
160 :
161 : #define UNIDOUBLER_HEADER "CStr.cpp"
162 : #include "UniDoubler.h"
163 :
164 :
165 : // Only include these function definitions in the first instance of CStr.cpp:
166 :
167 : /**
168 : * Convert CStr to UTF-8
169 : *
170 : * @return CStr8 converted string
171 : **/
172 1752 : CStr8 CStrW::ToUTF8() const
173 : {
174 : Status err;
175 1752 : return utf8_from_wstring(*this, &err);
176 : }
177 :
178 : /**
179 : * Convert UTF-8 to CStr
180 : *
181 : * @return CStrW converted string
182 : **/
183 33 : CStrW CStr8::FromUTF8() const
184 : {
185 : Status err;
186 33 : return wstring_from_utf8(*this, &err);
187 3 : }
188 :
189 : #else
190 :
191 : // The following code is compiled twice, as CStrW then as CStr8:
192 :
193 : #include "CStr.h"
194 :
195 0 : CStr CStr::Repeat(const CStr& str, size_t reps)
196 : {
197 0 : CStr ret;
198 0 : ret.reserve(str.length() * reps);
199 0 : while (reps--) ret += str;
200 0 : return ret;
201 : }
202 :
203 : // Construction from numbers:
204 :
205 0 : CStr CStr::FromInt(int n)
206 : {
207 0 : tstringstream<StrBase> ss;
208 0 : ss << n;
209 0 : return ss.str();
210 : }
211 :
212 8 : CStr CStr::FromUInt(unsigned int n)
213 : {
214 16 : tstringstream<StrBase> ss;
215 8 : ss << n;
216 16 : return ss.str();
217 : }
218 :
219 0 : CStr CStr::FromInt64(i64 n)
220 : {
221 0 : tstringstream<StrBase> ss;
222 0 : ss << n;
223 0 : return ss.str();
224 : }
225 :
226 0 : CStr CStr::FromDouble(double n)
227 : {
228 0 : tstringstream<StrBase> ss;
229 0 : ss << n;
230 0 : return ss.str();
231 : }
232 :
233 : // Conversion to numbers:
234 :
235 159 : int CStr::ToInt() const
236 : {
237 159 : int ret = 0;
238 318 : tstringstream<StrBase> str(*this);
239 159 : str >> ret;
240 318 : return ret;
241 : }
242 :
243 4 : unsigned int CStr::ToUInt() const
244 : {
245 4 : unsigned int ret = 0;
246 8 : tstringstream<StrBase> str(*this);
247 4 : str >> ret;
248 8 : return ret;
249 : }
250 :
251 5 : long CStr::ToLong() const
252 : {
253 5 : long ret = 0;
254 10 : tstringstream<StrBase> str(*this);
255 5 : str >> ret;
256 10 : return ret;
257 : }
258 :
259 4 : unsigned long CStr::ToULong() const
260 : {
261 4 : unsigned long ret = 0;
262 8 : tstringstream<StrBase> str(*this);
263 4 : str >> ret;
264 8 : return ret;
265 : }
266 :
267 : /**
268 : * libc++ and libstd++ differ on how they handle string-to-number parsing for floating-points numbers.
269 : * See https://trac.wildfiregames.com/ticket/2780#comment:4 for details.
270 : * To prevent this, only consider [0-9.-+], replace the others in-place with a neutral character.
271 : */
272 86 : CStr ParseableAsNumber(CStr cleaned_copy)
273 : {
274 274 : for (CStr::Char& c : cleaned_copy)
275 188 : if (!std::isdigit(c) && c != '.' && c != '-' && c != '+')
276 32 : c = ' ';
277 :
278 86 : return cleaned_copy;
279 : }
280 :
281 81 : float CStr::ToFloat() const
282 : {
283 81 : float ret = 0;
284 162 : tstringstream<StrBase> str(ParseableAsNumber(*this));
285 81 : str >> ret;
286 162 : return ret;
287 : }
288 :
289 5 : double CStr::ToDouble() const
290 : {
291 5 : double ret = 0;
292 10 : tstringstream<StrBase> str(ParseableAsNumber(*this));
293 5 : str >> ret;
294 10 : return ret;
295 : }
296 :
297 : // Search the string for another string
298 100 : long CStr::Find(const CStr& str) const
299 : {
300 100 : size_t pos = find(str, 0);
301 :
302 100 : if (pos != npos)
303 45 : return static_cast<long>(pos);
304 :
305 55 : return -1;
306 : }
307 :
308 : // Search the string for another string
309 0 : long CStr::Find(const Char chr) const
310 : {
311 0 : size_t pos = find(chr, 0);
312 :
313 0 : if (pos != npos)
314 0 : return static_cast<long>(pos);
315 :
316 0 : return -1;
317 : }
318 :
319 : // Search the string for another string
320 0 : long CStr::Find(const int start, const Char chr) const
321 : {
322 0 : size_t pos = find(chr, start);
323 :
324 0 : if (pos != npos)
325 0 : return static_cast<long>(pos);
326 :
327 0 : return -1;
328 : }
329 :
330 0 : long CStr::FindInsensitive(const int start, const Char chr) const { return LowerCase().Find(start, totlower(chr)); }
331 0 : long CStr::FindInsensitive(const Char chr) const { return LowerCase().Find(totlower(chr)); }
332 0 : long CStr::FindInsensitive(const CStr& str) const { return LowerCase().Find(str.LowerCase()); }
333 :
334 0 : long CStr::ReverseFind(const CStr& str) const
335 : {
336 0 : size_t pos = rfind(str, length() );
337 :
338 0 : if (pos != npos)
339 0 : return static_cast<long>(pos);
340 :
341 0 : return -1;
342 : }
343 :
344 : // Lowercase and uppercase
345 2850 : CStr CStr::LowerCase() const
346 : {
347 5700 : StrBase newStr = *this;
348 14350 : for (size_t i = 0; i < length(); i++)
349 11500 : newStr[i] = (Char)totlower((*this)[i]);
350 :
351 5700 : return newStr;
352 : }
353 :
354 13 : CStr CStr::UpperCase() const
355 : {
356 26 : StrBase newStr = *this;
357 845 : for (size_t i = 0; i < length(); i++)
358 832 : newStr[i] = (Char)totupper((*this)[i]);
359 :
360 26 : return newStr;
361 : }
362 :
363 :
364 : // Retrieve the substring of the first n characters
365 4 : CStr CStr::Left(size_t len) const
366 : {
367 4 : ENSURE(len <= length());
368 4 : return substr(0, len);
369 : }
370 :
371 : // Retrieve the substring of the last n characters
372 0 : CStr CStr::Right(size_t len) const
373 : {
374 0 : ENSURE(len <= length());
375 0 : return substr(length()-len, len);
376 : }
377 :
378 : // Retrieve the substring following the last occurrence of Str
379 : // (or the whole string if it doesn't contain Str)
380 0 : CStr CStr::AfterLast(const CStr& str, size_t startPos) const
381 : {
382 0 : size_t pos = rfind(str, startPos);
383 0 : if (pos == npos)
384 0 : return *this;
385 : else
386 0 : return substr(pos + str.length());
387 : }
388 :
389 : // Retrieve the substring preceding the last occurrence of Str
390 : // (or the whole string if it doesn't contain Str)
391 0 : CStr CStr::BeforeLast(const CStr& str, size_t startPos) const
392 : {
393 0 : size_t pos = rfind(str, startPos);
394 0 : if (pos == npos)
395 0 : return *this;
396 : else
397 0 : return substr(0, pos);
398 : }
399 :
400 : // Retrieve the substring following the first occurrence of Str
401 : // (or the whole string if it doesn't contain Str)
402 0 : CStr CStr::AfterFirst(const CStr& str, size_t startPos) const
403 : {
404 0 : size_t pos = find(str, startPos);
405 0 : if (pos == npos)
406 0 : return *this;
407 : else
408 0 : return substr(pos + str.length());
409 : }
410 :
411 : // Retrieve the substring preceding the first occurrence of Str
412 : // (or the whole string if it doesn't contain Str)
413 0 : CStr CStr::BeforeFirst(const CStr& str, size_t startPos) const
414 : {
415 0 : size_t pos = find(str, startPos);
416 0 : if (pos == npos)
417 0 : return *this;
418 : else
419 0 : return substr(0, pos);
420 : }
421 :
422 : // Remove all occurrences of some character or substring
423 0 : void CStr::Remove(const CStr& str)
424 : {
425 0 : size_t foundAt = 0;
426 0 : while (foundAt != npos)
427 : {
428 0 : foundAt = find(str, 0);
429 :
430 0 : if (foundAt != npos)
431 0 : erase(foundAt, str.length());
432 : }
433 0 : }
434 :
435 : // Replace all occurrences of some substring by another
436 55 : void CStr::Replace(const CStr& toReplace, const CStr& replaceWith)
437 : {
438 55 : size_t pos = 0;
439 189 : while (pos != npos)
440 : {
441 67 : pos = find(toReplace, pos);
442 67 : if (pos != npos)
443 : {
444 12 : erase(pos, toReplace.length());
445 12 : insert(pos, replaceWith);
446 12 : pos += replaceWith.length();
447 : }
448 : }
449 55 : }
450 :
451 0 : std::string CStr::EscapeToPrintableASCII() const
452 : {
453 0 : std::string newStr;
454 0 : for (size_t i = 0; i < length(); i++)
455 : {
456 0 : Char ch = (*this)[i];
457 :
458 0 : if (ch == '"') newStr += "\\\"";
459 0 : else if (ch == '\\') newStr += "\\\\";
460 0 : else if (ch == '\b') newStr += "\\b";
461 0 : else if (ch == '\f') newStr += "\\f";
462 0 : else if (ch == '\n') newStr += "\\n";
463 0 : else if (ch == '\r') newStr += "\\r";
464 0 : else if (ch == '\t') newStr += "\\t";
465 0 : else if (ch >= 32 && ch <= 126)
466 0 : newStr += ch;
467 : else
468 : {
469 0 : std::stringstream ss;
470 0 : ss << "\\u" << std::hex << std::setfill('0') << std::setw(4) << (int)(unsigned char)ch;
471 0 : newStr += ss.str();
472 : }
473 : }
474 0 : return newStr;
475 : }
476 :
477 : // Returns a trimmed string, removes whitespace from the left/right/both
478 8 : CStr CStr::Trim(PS_TRIM_MODE mode) const
479 : {
480 8 : size_t left = 0, right = 0;
481 :
482 8 : switch (mode)
483 : {
484 0 : case PS_TRIM_LEFT:
485 : {
486 0 : for (left = 0; left < length(); left++)
487 0 : if (istspace((*this)[left]) == false)
488 0 : break; // end found, trim 0 to Left-1 inclusive
489 0 : } break;
490 :
491 0 : case PS_TRIM_RIGHT:
492 : {
493 0 : right = length();
494 0 : while (right--)
495 0 : if (istspace((*this)[right]) == false)
496 0 : break; // end found, trim len-1 to Right+1 inclusive
497 0 : } break;
498 :
499 8 : case PS_TRIM_BOTH:
500 : {
501 77 : for (left = 0; left < length(); left++)
502 76 : if (istspace((*this)[left]) == false)
503 7 : break; // end found, trim 0 to Left-1 inclusive
504 :
505 8 : right = length();
506 46 : while (right--)
507 26 : if (istspace((*this)[right]) == false)
508 7 : break; // end found, trim len-1 to Right+1 inclusive
509 8 : } break;
510 :
511 0 : default:
512 0 : debug_warn(L"CStr::Trim: invalid Mode");
513 : }
514 :
515 :
516 8 : return substr(left, right - left + 1);
517 : }
518 :
519 0 : CStr CStr::Pad(PS_TRIM_MODE mode, size_t len) const
520 : {
521 0 : size_t left = 0, right = 0;
522 :
523 0 : if (len <= length())
524 0 : return *this;
525 :
526 : // From here: Length-length() >= 1
527 :
528 0 : switch (mode)
529 : {
530 0 : case PS_TRIM_LEFT:
531 0 : left = len - length();
532 0 : break;
533 :
534 0 : case PS_TRIM_RIGHT:
535 0 : right = len - length();
536 0 : break;
537 :
538 0 : case PS_TRIM_BOTH:
539 0 : left = (len - length() + 1) / 2;
540 0 : right = (len - length() - 1) / 2; // cannot be negative
541 0 : break;
542 :
543 0 : default:
544 0 : debug_warn(L"CStr::Trim: invalid Mode");
545 : }
546 :
547 0 : return StrBase(left, ' ') + *this + StrBase(right, ' ');
548 : }
549 :
550 84 : size_t CStr::GetHashCode() const
551 : {
552 84 : return (size_t)fnv_hash(data(), length()*sizeof(value_type));
553 : // janwas 2005-03-18: now use 32-bit version; 64 is slower and
554 : // the result was truncated down to 32 anyway.
555 : }
556 :
557 2 : u8* CStr::Serialize(u8* buffer) const
558 : {
559 2 : return SerializeImpl(*this, buffer);
560 : }
561 :
562 4 : const u8* CStr::Deserialize(const u8* buffer, const u8* bufferend)
563 : {
564 4 : return DeserializeImpl(buffer, bufferend, *this);
565 : }
566 :
567 2 : size_t CStr::GetSerializedLength() const
568 : {
569 2 : return GetSerializedLengthImpl(*this);
570 : }
571 :
572 : #endif // CStr_CPP_FIRST
|