/* 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