LCOV - code coverage report
Current view: top level - source/ps - CStr.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 115 252 45.6 %
Date: 2023-01-19 00:18:29 Functions: 28 88 31.8 %

          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

Generated by: LCOV version 1.13