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: 202 639 31.6 %
Date: 2022-06-14 00:41:00 Functions: 21 76 27.6 %

          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             :     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             :     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             :     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          20 :                 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           2 : 
     108             :     template<typename StrBase>
     109             :     const u8* DeserializeImpl(const u8* buffer, const u8* bufferend, StrBase& str)
     110           2 :     {
     111             :         using Char = typename StrBase::value_type;
     112             :         ENSURE(buffer);
     113             :         ENSURE(bufferend);
     114             :         if constexpr (std::is_same_v<Char, char>)
     115           2 :         {
     116           2 :             u32 len;
     117           2 :             Deserialize_int_4(buffer, len);
     118          12 :             if (buffer + len > bufferend)
     119          20 :                 return NULL;
     120           2 :             str = StrBase(buffer, buffer + len);
     121             :             return buffer + len;
     122             :         }
     123             :         else if constexpr (std::is_same_v<Char, wchar_t>)
     124             :         {
     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;
     130             : 
     131             :             str.resize(strend - (const u16 *)buffer);
     132             :             const u16 *ptr = (const u16 *)buffer;
     133             : 
     134             :             typename StrBase::iterator it = str.begin();
     135             :             while (ptr < strend)
     136             :             {
     137             :                 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             :     size_t GetSerializedLengthImpl(const StrBase& str)
     149             :     {
     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           0 :             static_assert(AlwaysFalse<Char>::value, "Not implemented.");
     157           0 :     }
     158           0 : 
     159             : } // anonymous namespace
     160           0 : 
     161           0 : #define UNIDOUBLER_HEADER "CStr.cpp"
     162             : #include "UniDoubler.h"
     163           0 : 
     164           0 : 
     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           4 :  **/
     172             : CStr8 CStrW::ToUTF8() const
     173             : {
     174           4 :     Status err;
     175           4 :     return utf8_from_wstring(*this, &err);
     176             : }
     177             : 
     178             : /**
     179           4 :  * Convert UTF-8 to CStr
     180           4 :  *
     181             :  * @return CStrW converted string
     182           4 :  **/
     183           4 : CStrW CStr8::FromUTF8() const
     184             : {
     185             :     Status err;
     186             :     return wstring_from_utf8(*this, &err);
     187           0 : }
     188           0 : 
     189           0 : #else
     190           0 : 
     191             : // The following code is compiled twice, as CStrW then as CStr8:
     192             : 
     193           0 : #include "CStr.h"
     194           0 : 
     195             : CStr CStr::Repeat(const CStr& str, size_t reps)
     196           0 : {
     197           0 :     CStr ret;
     198             :     ret.reserve(str.length() * reps);
     199           0 :     while (reps--) ret += str;
     200           0 :     return ret;
     201             : }
     202             : 
     203           0 : // Construction from numbers:
     204             : 
     205             : CStr CStr::FromInt(int n)
     206             : {
     207             :     tstringstream<StrBase> ss;
     208           4 :     ss << n;
     209             :     return ss.str();
     210             : }
     211           4 : 
     212           4 : CStr CStr::FromUInt(unsigned int n)
     213             : {
     214             :     tstringstream<StrBase> ss;
     215             :     ss << n;
     216           4 :     return ss.str();
     217           4 : }
     218             : 
     219           4 : CStr CStr::FromInt64(i64 n)
     220           4 : {
     221             :     tstringstream<StrBase> ss;
     222             :     ss << n;
     223             :     return ss.str();
     224             : }
     225             : 
     226             : CStr CStr::FromDouble(double n)
     227             : {
     228             :     tstringstream<StrBase> ss;
     229             :     ss << n;
     230             :     return ss.str();
     231             : }
     232             : 
     233             : // Conversion to numbers:
     234             : 
     235             : int CStr::ToInt() const
     236             : {
     237             :     int ret = 0;
     238             :     tstringstream<StrBase> str(*this);
     239             :     str >> ret;
     240             :     return ret;
     241             : }
     242             : 
     243             : unsigned int CStr::ToUInt() const
     244             : {
     245           0 :     unsigned int ret = 0;
     246             :     tstringstream<StrBase> str(*this);
     247             :     str >> ret;
     248           0 :     return ret;
     249           0 : }
     250             : 
     251             : long CStr::ToLong() const
     252             : {
     253             :     long ret = 0;
     254             :     tstringstream<StrBase> str(*this);
     255             :     str >> ret;
     256             :     return ret;
     257             : }
     258             : 
     259             : unsigned long CStr::ToULong() const
     260             : {
     261           0 :     unsigned long ret = 0;
     262           0 :     tstringstream<StrBase> str(*this);
     263           0 :     str >> ret;
     264           0 :     return ret;
     265             : }
     266             : 
     267           0 : /**
     268           0 :  * 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           0 :  * To prevent this, only consider [0-9.-+], replace the others in-place with a neutral character.
     271           0 :  */
     272             : CStr ParseableAsNumber(CStr cleaned_copy)
     273           0 : {
     274           0 :     for (CStr::Char& c : cleaned_copy)
     275             :         if (!std::isdigit(c) && c != '.' && c != '-' && c != '+')
     276             :             c = ' ';
     277           0 : 
     278             :     return cleaned_copy;
     279             : }
     280             : 
     281             : float CStr::ToFloat() const
     282             : {
     283             :     float ret = 0;
     284             :     tstringstream<StrBase> str(ParseableAsNumber(*this));
     285             :     str >> ret;
     286             :     return ret;
     287             : }
     288           2 : 
     289             : double CStr::ToDouble() const
     290           0 : {
     291             :     double ret = 0;
     292             :     tstringstream<StrBase> str(ParseableAsNumber(*this));
     293             :     str >> ret;
     294             :     return ret;
     295             : }
     296             : 
     297             : // Search the string for another string
     298             : long CStr::Find(const CStr& str) const
     299             : {
     300             :     size_t pos = find(str, 0);
     301             : 
     302             :     if (pos != npos)
     303             :         return static_cast<long>(pos);
     304             : 
     305             :     return -1;
     306             : }
     307             : 
     308        1602 : // Search the string for another string
     309             : long CStr::Find(const Char chr) const
     310        1602 : {
     311        2905 :     size_t pos = find(chr, 0);
     312             : 
     313             :     if (pos != npos)
     314             :         return static_cast<long>(pos);
     315             : 
     316             :     return -1;
     317             : }
     318             : 
     319          33 : // Search the string for another string
     320             : long CStr::Find(const int start, const Char chr) const
     321          33 : {
     322          56 :     size_t pos = find(chr, start);
     323             : 
     324             :     if (pos != npos)
     325             :         return static_cast<long>(pos);
     326             : 
     327             :     return -1;
     328             : }
     329             : 
     330             : 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             : long CStr::FindInsensitive(const CStr& str) const { return LowerCase().Find(str.LowerCase()); }
     333           0 : 
     334           0 : long CStr::ReverseFind(const CStr& str) const
     335           0 : {
     336           0 :     size_t pos = rfind(str, length() );
     337             : 
     338           0 :     if (pos != npos)
     339             :         return static_cast<long>(pos);
     340           0 : 
     341           0 :     return -1;
     342           0 : }
     343           0 : 
     344             : // Lowercase and uppercase
     345           0 : CStr CStr::LowerCase() const
     346             : {
     347           0 :     StrBase newStr = *this;
     348           0 :     for (size_t i = 0; i < length(); i++)
     349           0 :         newStr[i] = (Char)totlower((*this)[i]);
     350           0 : 
     351             :     return newStr;
     352             : }
     353             : 
     354             : CStr CStr::UpperCase() const
     355           0 : {
     356             :     StrBase newStr = *this;
     357           0 :     for (size_t i = 0; i < length(); i++)
     358           0 :         newStr[i] = (Char)totupper((*this)[i]);
     359           0 : 
     360             :     return newStr;
     361           0 : }
     362             : 
     363           0 : 
     364           0 : // Retrieve the substring of the first n characters
     365           0 : CStr CStr::Left(size_t len) const
     366             : {
     367           0 :     ENSURE(len <= length());
     368             :     return substr(0, len);
     369           0 : }
     370           0 : 
     371           0 : // Retrieve the substring of the last n characters
     372             : CStr CStr::Right(size_t len) const
     373             : {
     374           8 :     ENSURE(len <= length());
     375             :     return substr(length()-len, len);
     376           8 : }
     377           8 : 
     378           8 : // Retrieve the substring following the last occurrence of Str
     379             : // (or the whole string if it doesn't contain Str)
     380           8 : CStr CStr::AfterLast(const CStr& str, size_t startPos) const
     381             : {
     382           8 :     size_t pos = rfind(str, startPos);
     383           8 :     if (pos == npos)
     384           8 :         return *this;
     385             :     else
     386           0 :         return substr(pos + str.length());
     387             : }
     388           0 : 
     389           0 : // Retrieve the substring preceding the last occurrence of Str
     390           0 : // (or the whole string if it doesn't contain Str)
     391             : CStr CStr::BeforeLast(const CStr& str, size_t startPos) const
     392             : {
     393           0 :     size_t pos = rfind(str, startPos);
     394             :     if (pos == npos)
     395           0 :         return *this;
     396           0 :     else
     397           0 :         return substr(0, pos);
     398             : }
     399           0 : 
     400             : // Retrieve the substring following the first occurrence of Str
     401           0 : // (or the whole string if it doesn't contain Str)
     402           0 : CStr CStr::AfterFirst(const CStr& str, size_t startPos) const
     403           0 : {
     404             :     size_t pos = find(str, startPos);
     405           0 :     if (pos == npos)
     406             :         return *this;
     407           0 :     else
     408           0 :         return substr(pos + str.length());
     409           0 : }
     410             : 
     411             : // Retrieve the substring preceding the first occurrence of Str
     412           0 : // (or the whole string if it doesn't contain Str)
     413             : CStr CStr::BeforeFirst(const CStr& str, size_t startPos) const
     414           0 : {
     415           0 :     size_t pos = find(str, startPos);
     416           0 :     if (pos == npos)
     417             :         return *this;
     418           0 :     else
     419             :         return substr(0, pos);
     420           0 : }
     421           0 : 
     422           0 : // Remove all occurrences of some character or substring
     423             : void CStr::Remove(const CStr& str)
     424           0 : {
     425             :     size_t foundAt = 0;
     426           0 :     while (foundAt != npos)
     427           0 :     {
     428           0 :         foundAt = find(str, 0);
     429             : 
     430             :         if (foundAt != npos)
     431             :             erase(foundAt, str.length());
     432             :     }
     433         159 : }
     434             : 
     435         159 : // Replace all occurrences of some substring by another
     436         159 : void CStr::Replace(const CStr& toReplace, const CStr& replaceWith)
     437         159 : {
     438         159 :     size_t pos = 0;
     439             :     while (pos != npos)
     440         159 :     {
     441             :         pos = find(toReplace, pos);
     442         159 :         if (pos != npos)
     443         159 :         {
     444         159 :             erase(pos, toReplace.length());
     445         159 :             insert(pos, replaceWith);
     446             :             pos += replaceWith.length();
     447           0 :         }
     448             :     }
     449           0 : }
     450           0 : 
     451           0 : std::string CStr::EscapeToPrintableASCII() const
     452           0 : {
     453             :     std::string newStr;
     454             :     for (size_t i = 0; i < length(); i++)
     455           4 :     {
     456             :         Char ch = (*this)[i];
     457           4 : 
     458           4 :         if (ch == '"') newStr += "\\\"";
     459           4 :         else if (ch == '\\') newStr += "\\\\";
     460           4 :         else if (ch == '\b') newStr += "\\b";
     461             :         else if (ch == '\f') newStr += "\\f";
     462           4 :         else if (ch == '\n') newStr += "\\n";
     463             :         else if (ch == '\r') newStr += "\\r";
     464           4 :         else if (ch == '\t') newStr += "\\t";
     465           4 :         else if (ch >= 32 && ch <= 126)
     466           4 :             newStr += ch;
     467           4 :         else
     468             :         {
     469           0 :             std::stringstream ss;
     470             :             ss << "\\u" << std::hex << std::setfill('0') << std::setw(4) << (int)(unsigned char)ch;
     471           0 :             newStr += ss.str();
     472           0 :         }
     473           0 :     }
     474           0 :     return newStr;
     475             : }
     476             : 
     477           5 : // Returns a trimmed string, removes whitespace from the left/right/both
     478             : CStr CStr::Trim(PS_TRIM_MODE mode) const
     479           5 : {
     480           5 :     size_t left = 0, right = 0;
     481           5 : 
     482           5 :     switch (mode)
     483             :     {
     484           5 :         case PS_TRIM_LEFT:
     485             :         {
     486           5 :             for (left = 0; left < length(); left++)
     487           5 :                 if (istspace((*this)[left]) == false)
     488           5 :                     break; // end found, trim 0 to Left-1 inclusive
     489           5 :         } 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             :         } break;
     498             : 
     499           4 :         case PS_TRIM_BOTH:
     500             :         {
     501           4 :             for (left = 0; left < length(); left++)
     502           4 :                 if (istspace((*this)[left]) == false)
     503           4 :                     break; // end found, trim 0 to Left-1 inclusive
     504           4 : 
     505             :             right = length();
     506           4 :             while (right--)
     507             :                 if (istspace((*this)[right]) == false)
     508           4 :                     break; // end found, trim len-1 to Right+1  inclusive
     509           4 :         } break;
     510           4 : 
     511           4 :         default:
     512             :             debug_warn(L"CStr::Trim: invalid Mode");
     513           0 :     }
     514             : 
     515           0 : 
     516           0 :     return substr(left, right - left + 1);
     517           0 : }
     518           0 : 
     519             : CStr CStr::Pad(PS_TRIM_MODE mode, size_t len) const
     520             : {
     521             :     size_t left = 0, right = 0;
     522             : 
     523             :     if (len <= length())
     524             :         return *this;
     525             : 
     526          86 :     // From here: Length-length() >= 1
     527             : 
     528         446 :     switch (mode)
     529         188 :     {
     530          32 :     case PS_TRIM_LEFT:
     531             :         left = len - length();
     532          86 :         break;
     533             : 
     534          86 :     case PS_TRIM_RIGHT:
     535             :         right = len - length();
     536         446 :         break;
     537         188 : 
     538          32 :     case PS_TRIM_BOTH:
     539             :         left = (len - length() + 1) / 2;
     540          86 :         right = (len - length() - 1) / 2; // cannot be negative
     541             :         break;
     542           0 : 
     543             :     default:
     544           0 :         debug_warn(L"CStr::Trim: invalid Mode");
     545           0 :     }
     546           0 : 
     547             :     return StrBase(left, ' ') + *this + StrBase(right, ' ');
     548           0 : }
     549             : 
     550             : size_t CStr::GetHashCode() const
     551          81 : {
     552             :     return (size_t)fnv_hash(data(), length()*sizeof(value_type));
     553          81 :         // janwas 2005-03-18: now use 32-bit version; 64 is slower and
     554         162 :         // the result was truncated down to 32 anyway.
     555          81 : }
     556          81 : 
     557             : u8* CStr::Serialize(u8* buffer) const
     558          81 : {
     559             :     return SerializeImpl(*this, buffer);
     560          81 : }
     561         162 : 
     562          81 : const u8* CStr::Deserialize(const u8* buffer, const u8* bufferend)
     563          81 : {
     564             :     return DeserializeImpl(buffer, bufferend, *this);
     565           0 : }
     566             : 
     567           0 : size_t CStr::GetSerializedLength() const
     568           0 : {
     569           0 :     return GetSerializedLengthImpl(*this);
     570           0 : }
     571             : 
     572             : #endif // CStr_CPP_FIRST

Generated by: LCOV version 1.13