Files
FlaxEngine/Source/Engine/Platform/Base/StringUtilsBase.cpp
2024-02-26 19:00:48 +01:00

458 lines
12 KiB
C++

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "Engine/Platform/StringUtils.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Types/BaseTypes.h"
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Core/Collections/Array.h"
#if PLATFORM_TEXT_IS_CHAR16
#include <string>
#endif
constexpr char DirectorySeparatorChar = '\\';
constexpr char AltDirectorySeparatorChar = '/';
constexpr char VolumeSeparatorChar = ':';
const Char* StringUtils::FindIgnoreCase(const Char* str, const Char* toFind)
{
if (toFind == nullptr || str == nullptr)
{
return nullptr;
}
const Char findInitial = ToUpper(*toFind);
const int32 length = Length(toFind++) - 1;
Char c = *str++;
while (c)
{
c = ToUpper(c);
if (c == findInitial && !CompareIgnoreCase(str, toFind, length))
{
return str - 1;
}
c = *str++;
}
return nullptr;
}
const char* StringUtils::FindIgnoreCase(const char* str, const char* toFind)
{
if (toFind == nullptr || str == nullptr)
{
return nullptr;
}
const char findInitial = (char)ToUpper(*toFind);
const int32 length = Length(toFind++) - 1;
char c = *str++;
while (c)
{
c = (char)ToUpper(c);
if (c == findInitial && !CompareIgnoreCase(str, toFind, length))
{
return str - 1;
}
c = *str++;
}
return nullptr;
}
void PrintUTF8Error(const char* from, uint32 fromLength)
{
LOG(Error, "Not a UTF-8 string. Length: {0}", fromLength);
for (uint32 i = 0; i < fromLength; i++)
{
LOG(Error, "str[{0}] = {0}", i, (uint32)from[i]);
}
}
void ConvertUTF82UTF16Helper(Array<uint32>& unicode, const char* from, int32 fromLength, int32& toLength)
{
// Reference: https://stackoverflow.com/questions/7153935/how-to-convert-utf-8-stdstring-to-utf-16-stdwstring
unicode.EnsureCapacity(fromLength);
int32 i = 0, todo;
uint32 uni;
toLength = 0;
while (i < fromLength)
{
byte ch = from[i++];
if (ch <= 0x7F)
{
uni = ch;
todo = 0;
}
else if (ch <= 0xBF)
{
PrintUTF8Error(from, fromLength);
return;
}
else if (ch <= 0xDF)
{
uni = ch & 0x1F;
todo = 1;
}
else if (ch <= 0xEF)
{
uni = ch & 0x0F;
todo = 2;
}
else if (ch <= 0xF7)
{
uni = ch & 0x07;
todo = 3;
}
else
{
PrintUTF8Error(from, fromLength);
return;
}
for (int32 j = 0; j < todo; j++)
{
if (i == fromLength)
{
PrintUTF8Error(from, fromLength);
return;
}
ch = from[i++];
if (ch < 0x80 || ch > 0xBF)
{
PrintUTF8Error(from, fromLength);
return;
}
uni <<= 6;
uni += ch & 0x3F;
}
if ((uni >= 0xD800 && uni <= 0xDFFF) || uni > 0x10FFFF)
{
PrintUTF8Error(from, fromLength);
return;
}
unicode.Add(uni);
toLength++;
if (uni > 0xFFFF)
{
toLength++;
}
}
}
void StringUtils::ConvertUTF82UTF16(const char* from, Char* to, int32 fromLength, int32& toLength)
{
Array<uint32> unicode;
ConvertUTF82UTF16Helper(unicode, from, fromLength, toLength);
for (int32 i = 0, j = 0; j < unicode.Count(); i++, j++)
{
uint32 uni = unicode[j];
if (uni <= 0xFFFF)
{
to[i] = (Char)uni;
}
else
{
uni -= 0x10000;
to[i++] += (Char)((uni >> 10) + 0xD800);
to[i] += (Char)((uni & 0x3FF) + 0xDC00);
}
}
}
Char* StringUtils::ConvertUTF82UTF16(const char* from, int32 fromLength, int32& toLength)
{
Array<uint32> unicode;
ConvertUTF82UTF16Helper(unicode, from, fromLength, toLength);
if (toLength == 0)
return nullptr;
Char* to = (Char*)Allocator::Allocate((toLength + 1) * sizeof(Char));
for (int32 i = 0, j = 0; j < unicode.Count(); i++, j++)
{
uint32 uni = unicode[j];
if (uni <= 0xFFFF)
{
to[i] = (Char)uni;
}
else
{
uni -= 0x10000;
to[i++] += (Char)((uni >> 10) + 0xD800);
to[i] += (Char)((uni & 0x3FF) + 0xDC00);
}
}
to[toLength] = 0;
return to;
}
void PrintUTF16Error(const Char* from, uint32 fromLength)
{
LOG(Error, "Not a UTF-16 string. Length: {0}", fromLength);
for (uint32 i = 0; i < fromLength; i++)
{
LOG(Error, "str[{0}] = {0}", i, (uint32)from[i]);
}
}
void ConvertUTF162UTF8Helper(Array<uint32>& unicode, const Char* from, int32 fromLength, int32& toLength)
{
// Reference: https://stackoverflow.com/questions/21456926/how-do-i-convert-a-string-in-utf-16-to-utf-8-in-c
unicode.EnsureCapacity(fromLength);
toLength = 0;
int32 i = 0;
while (i < fromLength)
{
uint32 uni = from[i++];
if (uni < 0xD800U || uni > 0xDFFFU)
{
}
else if (uni >= 0xDC00U)
{
PrintUTF16Error(from, fromLength);
return;
}
else if (i + 1 == fromLength)
{
PrintUTF16Error(from, fromLength);
return;
}
else if (i < fromLength)
{
uni = (uni & 0x3FFU) << 10;
if ((from[i] < 0xDC00U) || (from[i] > 0xDFFFU))
{
PrintUTF16Error(from, fromLength);
return;
}
uni |= from[i++] & 0x3FFU;
uni += 0x10000U;
}
unicode.Add(uni);
toLength += uni <= 0x7FU ? 1 : uni <= 0x7FFU ? 2 : uni <= 0xFFFFU ? 3 : uni <= 0x1FFFFFU ? 4 : uni <= 0x3FFFFFFU ? 5 : uni <= 0x7FFFFFFFU ? 6 : 7;
}
}
void StringUtils::ConvertUTF162UTF8(const Char* from, char* to, int32 fromLength, int32& toLength)
{
Array<uint32> unicode;
ConvertUTF162UTF8Helper(unicode, from, fromLength, toLength);
for (int32 i = 0, j = 0; j < unicode.Count(); j++)
{
const uint32 uni = unicode[j];
const uint32 count = uni <= 0x7FU ? 1 : uni <= 0x7FFU ? 2 : uni <= 0xFFFFU ? 3 : uni <= 0x1FFFFFU ? 4 : uni <= 0x3FFFFFFU ? 5 : uni <= 0x7FFFFFFFU ? 6 : 7;
to[i++] = (char)(count <= 1 ? (byte)uni : ((byte(0xFFU) << (8 - count)) | byte(uni >> (6 * (count - 1)))));
for (uint32 k = 1; k < count; k++)
to[i++] = char(byte(0x80U | (byte(0x3FU) & byte(uni >> (6 * (count - 1 - k))))));
}
}
char* StringUtils::ConvertUTF162UTF8(const Char* from, int32 fromLength, int32& toLength)
{
Array<uint32> unicode;
ConvertUTF162UTF8Helper(unicode, from, fromLength, toLength);
if (toLength == 0)
return nullptr;
char* to = (char*)Allocator::Allocate(toLength + 1);
for (int32 i = 0, j = 0; j < unicode.Count(); j++)
{
const uint32 uni = unicode[j];
const uint32 count = uni <= 0x7FU ? 1 : uni <= 0x7FFU ? 2 : uni <= 0xFFFFU ? 3 : uni <= 0x1FFFFFU ? 4 : uni <= 0x3FFFFFFU ? 5 : uni <= 0x7FFFFFFFU ? 6 : 7;
to[i++] = (char)(count <= 1 ? (byte)uni : ((byte(0xFFU) << (8 - count)) | byte(uni >> (6 * (count - 1)))));
for (uint32 k = 1; k < count; k++)
to[i++] = char(byte(0x80U | (byte(0x3FU) & byte(uni >> (6 * (count - 1 - k))))));
}
to[toLength] = 0;
return to;
}
void RemoveLongPathPrefix(const String& path, String& result)
{
if (!path.StartsWith(TEXT("\\\\?\\"), StringSearchCase::CaseSensitive))
{
result = path;
}
if (!path.StartsWith(TEXT("\\\\?\\UNC\\"), StringSearchCase::IgnoreCase))
{
result = path.Substring(4);
}
result = path;
result.Remove(2, 6);
}
StringView StringUtils::GetDirectoryName(const StringView& path)
{
const int32 lastFrontSlash = path.FindLast('\\');
const int32 lastBackSlash = path.FindLast('/');
const int32 splitIndex = Math::Max(lastBackSlash, lastFrontSlash);
return splitIndex != INVALID_INDEX ? path.Left(splitIndex) : StringView::Empty;
}
StringView StringUtils::GetFileName(const StringView& path)
{
Char chr;
const int32 length = path.Length();
int32 num = length;
do
{
num--;
if (num < 0)
return path;
chr = path[num];
} while (chr != DirectorySeparatorChar && chr != AltDirectorySeparatorChar && chr != VolumeSeparatorChar);
return path.Substring(num + 1, length - num - 1);
}
StringView StringUtils::GetFileNameWithoutExtension(const StringView& path)
{
StringView filename = GetFileName(path);
const int32 num = filename.FindLast('.');
if (num != -1)
return filename.Substring(0, num);
return filename;
}
StringView StringUtils::GetPathWithoutExtension(const StringView& path)
{
const int32 num = path.FindLast('.');
if (num != -1)
return path.Substring(0, num);
return path;
}
void StringUtils::PathRemoveRelativeParts(String& path)
{
FileSystem::NormalizePath(path);
Array<String> components;
path.Split(TEXT('/'), components);
Array<String, InlinedAllocation<16>> stack;
for (String& bit : components)
{
if (bit == TEXT(".."))
{
if (stack.HasItems())
{
String popped = stack.Pop();
const int32 windowsDriveStart = popped.Find('\\');
if (popped == TEXT(".."))
{
stack.Push(popped);
stack.Push(bit);
}
else if (windowsDriveStart != -1)
{
stack.Add(MoveTemp(popped.Left(windowsDriveStart + 1)));
}
}
else
{
stack.Push(bit);
}
}
else if (bit == TEXT("."))
{
// Skip /./
}
else
{
stack.Add(MoveTemp(bit));
}
}
const bool isRooted = path.StartsWith(TEXT('/')) || (path.Length() >= 2 && path[0] == '.' && path[1] == '/');
path.Clear();
for (const String& e : stack)
path /= e;
if (isRooted && path.HasChars() && path[0] != '/')
path.Insert(0, TEXT("/"));
}
int32 StringUtils::HexDigit(Char c)
{
int32 result = 0;
if (c >= '0' && c <= '9')
result = c - '0';
else if (c >= 'a' && c <= 'f')
result = c + 10 - 'a';
else if (c >= 'A' && c <= 'F')
result = c + 10 - 'A';
return result;
}
bool StringUtils::Parse(const Char* str, float* result)
{
#if PLATFORM_TEXT_IS_CHAR16
std::u16string u16str = str;
std::wstring wstr(u16str.begin(), u16str.end());
float v = wcstof(wstr.c_str(), nullptr);
#else
float v = wcstof(str, nullptr);
#endif
*result = v;
if (v == 0)
{
const int32 len = Length(str);
return !(str[0] == '0' && ((len == 1) || (len == 3 && (str[1] == ',' || str[1] == '.') && str[2] == '0')));
}
return false;
}
bool StringUtils::Parse(const char* str, float* result)
{
*result = (float)atof(str);
return false;
}
String StringUtils::ToString(int32 value)
{
return String::Format(TEXT("{}"), value);
}
String StringUtils::ToString(int64 value)
{
return String::Format(TEXT("{}"), value);
}
String StringUtils::ToString(uint32 value)
{
return String::Format(TEXT("{}"), value);
}
String StringUtils::ToString(uint64 value)
{
return String::Format(TEXT("{}"), value);
}
String StringUtils::ToString(float value)
{
return String::Format(TEXT("{}"), value);
}
String StringUtils::ToString(double value)
{
return String::Format(TEXT("{}"), value);
}
String StringUtils::GetZZString(const Char* str)
{
const Char* end = str;
while (*end != '\0')
{
end++;
if (*end == '\0')
end++;
}
return String(str, (int32)(end - str));
}