/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program 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, either version 3 of the License, or * (at your option) any later version. * * This program 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "ags/shared/util/string_utils.h" #include "ags/shared/util/utf8.h" #include "ags/shared/core/platform.h" #include "common/std/regex.h" #include "ags/shared/util/math.h" #include "ags/shared/util/stream.h" #include "ags/shared/util/string_compat.h" #include "ags/globals.h" namespace AGS3 { using namespace AGS::Shared; namespace AGS { namespace Shared { String StrUtil::IntToString(int d) { return String::FromFormat("%d", d); } int StrUtil::StringToInt(const String &s, int def_val) { if (s.IsEmpty()) return def_val; char *stop_ptr; int val = strtol(s.GetCStr(), &stop_ptr, 0); return (stop_ptr == s.GetCStr() + s.GetLength()) ? val : def_val; } StrUtil::ConversionError StrUtil::StringToInt(const String &s, int &val, int def_val) { val = def_val; if (s.IsEmpty()) return StrUtil::kFailed; char *stop_ptr; _G(errnum) = 0; long lval = strtol(s.GetCStr(), &stop_ptr, 0); if (stop_ptr != s.GetCStr() + s.GetLength()) return StrUtil::kFailed; if (lval > INT_MAX || lval < INT_MIN || _G(errnum) == AL_ERANGE) return StrUtil::kOutOfRange; val = static_cast(lval); return StrUtil::kNoError; } float StrUtil::StringToFloat(const String &s, float def_val) { if (s.IsEmpty()) return def_val; char *stop_ptr; float val = strtof(s.GetCStr(), &stop_ptr); return (stop_ptr == s.GetCStr() + s.GetLength()) ? val : def_val; } String StrUtil::Unescape(const String &s) { size_t at = s.FindChar('\\'); if (at == String::NoIndex) return s; // no unescaping necessary, return original string char *buf = new char[s.GetLength()]; strncpy(buf, s.GetCStr(), at); char *pb = buf + at; for (const char *ptr = s.GetCStr() + at; *ptr; ++ptr) { if (*ptr != '\\') { *(pb++) = *ptr; continue; } char next = *(++ptr); switch (next) { case 'a': *(pb++) = '\a'; break; case 'b': *(pb++) = '\b'; break; case 'f': *(pb++) = '\f'; break; case 'n': *(pb++) = '\n'; break; case 'r': *(pb++) = '\r'; break; case 't': *(pb++) = '\t'; break; case 'v': *(pb++) = '\v'; break; case '\\': *(pb++) = '\\'; break; case '\'': *(pb++) = '\''; break; case '\"': *(pb++) = '\"'; break; case '\?': *(pb++) = '\?'; break; default: *(pb++) = next; break; } } *pb = 0; String dst(buf); delete[] buf; return dst; } String StrUtil::WildcardToRegex(const String &wildcard) { // https://stackoverflow.com/questions/40195412/c11-regex-search-for-exact-string-escape // matches any characters that need to be escaped in RegEx std::regex esc{ R"([-[\]{}()*+?.,\^$|#\s])" }; Common::String sanitized = std::regex_replace(wildcard.GetCStr(), esc, R"(\$&)"); // convert (now escaped) wildcard "\\*" and "\\?" into ".*" and "." respectively String pattern(sanitized.c_str()); pattern.Replace("\\*", ".*"); pattern.Replace("\\?", "."); return pattern; } String StrUtil::ReadString(Stream *in) { size_t len = in->ReadInt32(); if (len > 0) return String::FromStreamCount(in, len); return String(); } void StrUtil::ReadString(char *cstr, Stream *in, size_t buf_limit) { size_t len = in->ReadInt32(); if (buf_limit == 0) { in->Seek(len); return; } len = MIN(len, buf_limit - 1); if (len > 0) in->Read(cstr, len); cstr[len] = 0; } void StrUtil::ReadString(String &s, Stream *in) { size_t len = in->ReadInt32(); s.ReadCount(in, len); } void StrUtil::ReadString(char **cstr, Stream *in) { size_t len = in->ReadInt32(); *cstr = new char[len + 1]; if (len > 0) in->Read(*cstr, len); (*cstr)[len] = 0; } String StrUtil::ReadStringAligned(Stream *in) { String s = ReadString(in); size_t rem = s.GetLength() % sizeof(int32_t); if (rem > 0) { size_t pad = sizeof(int32_t) - rem; for (size_t i = 0; i < pad; ++i) in->ReadByte(); } return s; } void StrUtil::SkipString(Stream *in) { size_t len = in->ReadInt32(); in->Seek(len); } void StrUtil::WriteString(const String &s, Stream *out) { size_t len = s.GetLength(); out->WriteInt32(len); if (len > 0) out->Write(s.GetCStr(), len); } void StrUtil::WriteString(const char *cstr, Stream *out) { size_t len = strlen(cstr); out->WriteInt32(len); if (len > 0) out->Write(cstr, len); } void StrUtil::WriteString(const char *cstr, size_t len, Stream *out) { out->WriteInt32(len); if (len > 0) out->Write(cstr, len); } void StrUtil::ReadCStr(char *buf, Stream *in, size_t buf_limit) { if (buf_limit == 0) { while (in->ReadByte() > 0); return; } auto ptr = buf; auto last = buf + buf_limit - 1; for (;;) { if (ptr >= last) { *ptr = 0; while (in->ReadByte() > 0); // must still read until 0 break; } auto ichar = in->ReadByte(); if (ichar <= 0) { *ptr = 0; break; } *ptr = static_cast(ichar); ptr++; } } void StrUtil::ReadCStrCount(char *buf, Stream *in, size_t count) { in->Read(buf, count); buf[count - 1] = 0; // for safety } char *StrUtil::ReadMallocCStrOrNull(Stream *in) { char buf[1024]; for (auto ptr = buf; (ptr < buf + sizeof(buf)); ++ptr) { auto ichar = in->ReadByte(); if (ichar <= 0) { *ptr = 0; break; } *ptr = static_cast(ichar); } return buf[0] != 0 ? ags_strdup(buf) : nullptr; } void StrUtil::SkipCStr(Stream *in) { while (in->ReadByte() > 0); } void StrUtil::WriteCStr(const char *cstr, Stream *out) { if (cstr) out->Write(cstr, strlen(cstr) + 1); else out->WriteByte(0); } void StrUtil::WriteCStr(const String &s, Stream *out) { out->Write(s.GetCStr(), s.GetLength() + 1); } void StrUtil::ReadStringMap(StringMap &map, Stream *in) { size_t count = in->ReadInt32(); for (size_t i = 0; i < count; ++i) { String key = StrUtil::ReadString(in); String value = StrUtil::ReadString(in); map.insert(std::make_pair(key, value)); } } void StrUtil::WriteStringMap(const StringMap &map, Stream *out) { out->WriteInt32(map.size()); for (const auto &kv : map) { StrUtil::WriteString(kv._key, out); StrUtil::WriteString(kv._value, out); } } size_t StrUtil::ConvertUtf8ToAscii(const char *mbstr, const char *loc_name, char *out_cstr, size_t out_sz) { // TODO: later consider using alternative conversion methods // (e.g. see C++11 features), as setlocale is unreliable. char old_locale[64]; snprintf(old_locale, sizeof(old_locale), "%s", setlocale(LC_CTYPE, nullptr)); if (setlocale(LC_CTYPE, loc_name) == nullptr) { // If failed setlocale, then resort to plain copy the mb string return static_cast(snprintf(out_cstr, out_sz, "%s", mbstr)); } // First convert utf-8 string into widestring; std::vector wcsbuf; // widechar buffer wcsbuf.resize(Utf8::GetLength(mbstr) + 1); // NOTE: we don't use mbstowcs, because unfortunately ".utf-8" locale // is not normally supported on all systems (e.g. Windows 7 and earlier) for (size_t at = 0, chr_sz = 0; *mbstr; mbstr += chr_sz, ++at) { Utf8::Rune r; chr_sz = Utf8::GetChar(mbstr, Utf8::UtfSz, &r); wcsbuf[at] = static_cast(r); } // Then convert widestring to single-byte string using specified locale size_t res_sz = wcstombs(out_cstr, &wcsbuf[0], out_sz); setlocale(LC_CTYPE, old_locale); return res_sz; } size_t StrUtil::ConvertUtf8ToWstr(const char *mbstr, wchar_t *out_wcstr, size_t out_sz) { size_t len = 0; for (size_t mb_sz = 1; *mbstr && (mb_sz > 0) && (len < out_sz); mbstr += mb_sz, ++out_wcstr, ++len) { Utf8::Rune r; mb_sz = Utf8::GetChar(mbstr, Utf8::UtfSz, &r); *out_wcstr = static_cast(r); } *out_wcstr = 0; return len; } size_t StrUtil::ConvertWstrToUtf8(const wchar_t *wcstr, char *out_mbstr, size_t out_sz) { size_t len = 0; for (size_t mb_sz = 1; *wcstr && (mb_sz > 0) && (len + mb_sz < out_sz); ++wcstr, out_mbstr += mb_sz, len += mb_sz) { mb_sz = Utf8::SetChar(*wcstr, out_mbstr, out_sz - len); } *out_mbstr = 0; return len; } } // namespace Shared } // namespace AGS } // namespace AGS3