Civ backgrounds for minimap
[0ad.git] / source / ps / CStr.cpp
blob6bba5c42df15b7ba2ce30c80641572193f755b2d
1 /* Copyright (C) 2021 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #ifndef CStr_CPP_FIRST
21 #define CStr_CPP_FIRST
23 #include "lib/fnv_hash.h"
24 #include "lib/utf8.h"
25 #include "lib/byte_order.h"
26 #include "network/Serialization.h"
28 #include <cctype>
29 #include <cwctype>
30 #include <iomanip>
31 #include <sstream>
32 #include <type_traits>
34 namespace
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 {};
45 template<typename StrBase>
46 using tstringstream = std::basic_stringstream<typename StrBase::value_type>;
48 template<typename Char>
49 bool istspace(const Char chr)
51 if constexpr (std::is_same_v<Char, char>)
52 return static_cast<bool>(std::isspace(chr));
53 else
54 return static_cast<bool>(std::iswspace(chr));
57 template<typename Char>
58 Char totlower(const Char chr)
60 if constexpr (std::is_same_v<Char, char>)
61 return std::tolower(chr);
62 else
63 return std::towlower(chr);
66 template<typename Char>
67 Char totupper(const Char chr)
69 if constexpr (std::is_same_v<Char, char>)
70 return std::toupper(chr);
71 else
72 return std::towupper(chr);
75 template<typename StrBase>
76 u8* SerializeImpl(const StrBase& str, u8* buffer)
78 using Char = typename StrBase::value_type;
79 ENSURE(buffer);
80 if constexpr (std::is_same_v<Char, char>)
82 // CStr8 is always serialized to / from ASCII(or whatever 8 - bit codepage stored
83 // in the CStr).
84 size_t len = str.length();
85 Serialize_int_4(buffer, (u32)len);
86 size_t i = 0;
87 for (i = 0; i < len; i++)
88 buffer[i] = str[i];
89 return buffer + len;
91 else if constexpr (std::is_same_v<Char, wchar_t>)
93 // CStrW is always serialized to / from UTF - 16.
94 size_t len = str.length();
95 size_t i = 0;
96 for (i = 0; i < len; i++)
98 const u16 bigEndian = to_be16(str[i]);
99 *(u16 *)(buffer + i * 2) = bigEndian;
101 *(u16 *)(buffer + i * 2) = 0;
102 return buffer + len * 2 + 2;
104 else
105 static_assert(AlwaysFalse<Char>::value, "Not implemented.");
108 template<typename StrBase>
109 const u8* DeserializeImpl(const u8* buffer, const u8* bufferend, StrBase& str)
111 using Char = typename StrBase::value_type;
112 ENSURE(buffer);
113 ENSURE(bufferend);
114 if constexpr (std::is_same_v<Char, char>)
116 u32 len;
117 Deserialize_int_4(buffer, len);
118 if (buffer + len > bufferend)
119 return NULL;
120 str = StrBase(buffer, buffer + len);
121 return buffer + len;
123 else if constexpr (std::is_same_v<Char, wchar_t>)
125 const u16 *strend = (const u16 *)buffer;
126 while ((const u8 *)strend < bufferend && *strend)
127 strend++;
128 if ((const u8 *)strend >= bufferend)
129 return nullptr;
131 str.resize(strend - (const u16 *)buffer);
132 const u16 *ptr = (const u16 *)buffer;
134 typename StrBase::iterator it = str.begin();
135 while (ptr < strend)
137 const u16 native = to_be16(*(ptr++)); // we want from_be16, but that's the same
138 *(it++) = (Char)native;
141 return (const u8 *)(strend + 1);
143 else
144 static_assert(AlwaysFalse<Char>::value, "Not implemented.");
147 template<typename StrBase>
148 size_t GetSerializedLengthImpl(const StrBase& str)
150 using Char = typename StrBase::value_type;
151 if constexpr (std::is_same_v<Char, char>)
152 return str.length() + 4;
153 else if constexpr (std::is_same_v<Char, wchar_t>)
154 return str.length() * 2 + 2;
155 else
156 static_assert(AlwaysFalse<Char>::value, "Not implemented.");
159 } // anonymous namespace
161 #define UNIDOUBLER_HEADER "CStr.cpp"
162 #include "UniDoubler.h"
165 // Only include these function definitions in the first instance of CStr.cpp:
168 * Convert CStr to UTF-8
170 * @return CStr8 converted string
172 CStr8 CStrW::ToUTF8() const
174 Status err;
175 return utf8_from_wstring(*this, &err);
179 * Convert UTF-8 to CStr
181 * @return CStrW converted string
183 CStrW CStr8::FromUTF8() const
185 Status err;
186 return wstring_from_utf8(*this, &err);
189 #else
191 // The following code is compiled twice, as CStrW then as CStr8:
193 #include "CStr.h"
195 CStr CStr::Repeat(const CStr& str, size_t reps)
197 CStr ret;
198 ret.reserve(str.length() * reps);
199 while (reps--) ret += str;
200 return ret;
203 // Construction from numbers:
205 CStr CStr::FromInt(int n)
207 tstringstream<StrBase> ss;
208 ss << n;
209 return ss.str();
212 CStr CStr::FromUInt(unsigned int n)
214 tstringstream<StrBase> ss;
215 ss << n;
216 return ss.str();
219 CStr CStr::FromInt64(i64 n)
221 tstringstream<StrBase> ss;
222 ss << n;
223 return ss.str();
226 CStr CStr::FromDouble(double n)
228 tstringstream<StrBase> ss;
229 ss << n;
230 return ss.str();
233 // Conversion to numbers:
235 int CStr::ToInt() const
237 int ret = 0;
238 tstringstream<StrBase> str(*this);
239 str >> ret;
240 return ret;
243 unsigned int CStr::ToUInt() const
245 unsigned int ret = 0;
246 tstringstream<StrBase> str(*this);
247 str >> ret;
248 return ret;
251 long CStr::ToLong() const
253 long ret = 0;
254 tstringstream<StrBase> str(*this);
255 str >> ret;
256 return ret;
259 unsigned long CStr::ToULong() const
261 unsigned long ret = 0;
262 tstringstream<StrBase> str(*this);
263 str >> ret;
264 return ret;
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.
272 CStr ParseableAsNumber(CStr cleaned_copy)
274 for (CStr::Char& c : cleaned_copy)
275 if (!std::isdigit(c) && c != '.' && c != '-' && c != '+')
276 c = ' ';
278 return cleaned_copy;
281 float CStr::ToFloat() const
283 float ret = 0;
284 tstringstream<StrBase> str(ParseableAsNumber(*this));
285 str >> ret;
286 return ret;
289 double CStr::ToDouble() const
291 double ret = 0;
292 tstringstream<StrBase> str(ParseableAsNumber(*this));
293 str >> ret;
294 return ret;
297 // Search the string for another string
298 long CStr::Find(const CStr& str) const
300 size_t pos = find(str, 0);
302 if (pos != npos)
303 return static_cast<long>(pos);
305 return -1;
308 // Search the string for another string
309 long CStr::Find(const Char chr) const
311 size_t pos = find(chr, 0);
313 if (pos != npos)
314 return static_cast<long>(pos);
316 return -1;
319 // Search the string for another string
320 long CStr::Find(const int start, const Char chr) const
322 size_t pos = find(chr, start);
324 if (pos != npos)
325 return static_cast<long>(pos);
327 return -1;
330 long CStr::FindInsensitive(const int start, const Char chr) const { return LowerCase().Find(start, totlower(chr)); }
331 long CStr::FindInsensitive(const Char chr) const { return LowerCase().Find(totlower(chr)); }
332 long CStr::FindInsensitive(const CStr& str) const { return LowerCase().Find(str.LowerCase()); }
334 long CStr::ReverseFind(const CStr& str) const
336 size_t pos = rfind(str, length() );
338 if (pos != npos)
339 return static_cast<long>(pos);
341 return -1;
344 // Lowercase and uppercase
345 CStr CStr::LowerCase() const
347 StrBase newStr = *this;
348 for (size_t i = 0; i < length(); i++)
349 newStr[i] = (Char)totlower((*this)[i]);
351 return newStr;
354 CStr CStr::UpperCase() const
356 StrBase newStr = *this;
357 for (size_t i = 0; i < length(); i++)
358 newStr[i] = (Char)totupper((*this)[i]);
360 return newStr;
364 // Retrieve the substring of the first n characters
365 CStr CStr::Left(size_t len) const
367 ENSURE(len <= length());
368 return substr(0, len);
371 // Retrieve the substring of the last n characters
372 CStr CStr::Right(size_t len) const
374 ENSURE(len <= length());
375 return substr(length()-len, len);
378 // Retrieve the substring following the last occurrence of Str
379 // (or the whole string if it doesn't contain Str)
380 CStr CStr::AfterLast(const CStr& str, size_t startPos) const
382 size_t pos = rfind(str, startPos);
383 if (pos == npos)
384 return *this;
385 else
386 return substr(pos + str.length());
389 // Retrieve the substring preceding the last occurrence of Str
390 // (or the whole string if it doesn't contain Str)
391 CStr CStr::BeforeLast(const CStr& str, size_t startPos) const
393 size_t pos = rfind(str, startPos);
394 if (pos == npos)
395 return *this;
396 else
397 return substr(0, pos);
400 // Retrieve the substring following the first occurrence of Str
401 // (or the whole string if it doesn't contain Str)
402 CStr CStr::AfterFirst(const CStr& str, size_t startPos) const
404 size_t pos = find(str, startPos);
405 if (pos == npos)
406 return *this;
407 else
408 return substr(pos + str.length());
411 // Retrieve the substring preceding the first occurrence of Str
412 // (or the whole string if it doesn't contain Str)
413 CStr CStr::BeforeFirst(const CStr& str, size_t startPos) const
415 size_t pos = find(str, startPos);
416 if (pos == npos)
417 return *this;
418 else
419 return substr(0, pos);
422 // Remove all occurrences of some character or substring
423 void CStr::Remove(const CStr& str)
425 size_t foundAt = 0;
426 while (foundAt != npos)
428 foundAt = find(str, 0);
430 if (foundAt != npos)
431 erase(foundAt, str.length());
435 // Replace all occurrences of some substring by another
436 void CStr::Replace(const CStr& toReplace, const CStr& replaceWith)
438 size_t pos = 0;
439 while (pos != npos)
441 pos = find(toReplace, pos);
442 if (pos != npos)
444 erase(pos, toReplace.length());
445 insert(pos, replaceWith);
446 pos += replaceWith.length();
451 std::string CStr::EscapeToPrintableASCII() const
453 std::string newStr;
454 for (size_t i = 0; i < length(); i++)
456 Char ch = (*this)[i];
458 if (ch == '"') newStr += "\\\"";
459 else if (ch == '\\') newStr += "\\\\";
460 else if (ch == '\b') newStr += "\\b";
461 else if (ch == '\f') newStr += "\\f";
462 else if (ch == '\n') newStr += "\\n";
463 else if (ch == '\r') newStr += "\\r";
464 else if (ch == '\t') newStr += "\\t";
465 else if (ch >= 32 && ch <= 126)
466 newStr += ch;
467 else
469 std::stringstream ss;
470 ss << "\\u" << std::hex << std::setfill('0') << std::setw(4) << (int)(unsigned char)ch;
471 newStr += ss.str();
474 return newStr;
477 // Returns a trimmed string, removes whitespace from the left/right/both
478 CStr CStr::Trim(PS_TRIM_MODE mode) const
480 size_t left = 0, right = 0;
482 switch (mode)
484 case PS_TRIM_LEFT:
486 for (left = 0; left < length(); left++)
487 if (istspace((*this)[left]) == false)
488 break; // end found, trim 0 to Left-1 inclusive
489 } break;
491 case PS_TRIM_RIGHT:
493 right = length();
494 while (right--)
495 if (istspace((*this)[right]) == false)
496 break; // end found, trim len-1 to Right+1 inclusive
497 } break;
499 case PS_TRIM_BOTH:
501 for (left = 0; left < length(); left++)
502 if (istspace((*this)[left]) == false)
503 break; // end found, trim 0 to Left-1 inclusive
505 right = length();
506 while (right--)
507 if (istspace((*this)[right]) == false)
508 break; // end found, trim len-1 to Right+1 inclusive
509 } break;
511 default:
512 debug_warn(L"CStr::Trim: invalid Mode");
516 return substr(left, right - left + 1);
519 CStr CStr::Pad(PS_TRIM_MODE mode, size_t len) const
521 size_t left = 0, right = 0;
523 if (len <= length())
524 return *this;
526 // From here: Length-length() >= 1
528 switch (mode)
530 case PS_TRIM_LEFT:
531 left = len - length();
532 break;
534 case PS_TRIM_RIGHT:
535 right = len - length();
536 break;
538 case PS_TRIM_BOTH:
539 left = (len - length() + 1) / 2;
540 right = (len - length() - 1) / 2; // cannot be negative
541 break;
543 default:
544 debug_warn(L"CStr::Trim: invalid Mode");
547 return StrBase(left, ' ') + *this + StrBase(right, ' ');
550 size_t CStr::GetHashCode() const
552 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.
557 u8* CStr::Serialize(u8* buffer) const
559 return SerializeImpl(*this, buffer);
562 const u8* CStr::Deserialize(const u8* buffer, const u8* bufferend)
564 return DeserializeImpl(buffer, bufferend, *this);
567 size_t CStr::GetSerializedLength() const
569 return GetSerializedLengthImpl(*this);
572 #endif // CStr_CPP_FIRST