Files
FlaxEngine/Source/Engine/Platform/Base/StringUtilsBase.cpp
Ari Vuollet 0ac5db45d1 Improve Linux file dialog support
Adds support for multi selection, filtering and initial folder for both zenity and kdialog. kdialog is also preferred over zenity when KDE is detected.
2023-02-03 22:27:27 +02:00

545 lines
14 KiB
C++

// Copyright (c) 2012-2023 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
const char DirectorySeparatorChar = '\\';
const char AltDirectorySeparatorChar = '/';
const 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 (auto& bit : components)
{
if (bit == TEXT(".."))
{
if (stack.HasItems())
{
auto popped = stack.Pop();
if (popped == TEXT(".."))
{
stack.Push(popped);
stack.Push(bit);
}
}
else
{
stack.Push(bit);
}
}
else if (bit == TEXT("."))
{
// Skip /./
}
else
{
stack.Push(bit);
}
}
const bool isRooted = path.StartsWith(TEXT('/')) || (path.Length() >= 2 && path[0] == '.' && path[1] == '/');
path.Clear();
for (auto& e : stack)
path /= e;
if (isRooted && path.HasChars() && path[0] != '/')
path.Insert(0, TEXT("/"));
}
const char DigitPairs[201] = {
"00010203040506070809"
"10111213141516171819"
"20212223242526272829"
"30313233343536373839"
"40414243444546474849"
"50515253545556575859"
"60616263646566676869"
"70717273747576777879"
"80818283848586878889"
"90919293949596979899"
};
#define STRING_UTILS_ITOSTR_BUFFER_SIZE 15
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)
{
char buf[STRING_UTILS_ITOSTR_BUFFER_SIZE];
char* it = &buf[STRING_UTILS_ITOSTR_BUFFER_SIZE - 2];
int32 div = value / 100;
if (value >= 0)
{
while (div)
{
Platform::MemoryCopy(it, &DigitPairs[2 * (value - div * 100)], 2);
value = div;
it -= 2;
div = value / 100;
}
Platform::MemoryCopy(it, &DigitPairs[2 * value], 2);
if (value < 10)
it++;
}
else
{
while (div)
{
Platform::MemoryCopy(it, &DigitPairs[-2 * (value - div * 100)], 2);
value = div;
it -= 2;
div = value / 100;
}
Platform::MemoryCopy(it, &DigitPairs[-2 * value], 2);
if (value <= -10)
it--;
*it = '-';
}
return String(it, (int32)(&buf[STRING_UTILS_ITOSTR_BUFFER_SIZE] - it));
}
String StringUtils::ToString(int64 value)
{
char buf[STRING_UTILS_ITOSTR_BUFFER_SIZE];
char* it = &buf[STRING_UTILS_ITOSTR_BUFFER_SIZE - 2];
int64 div = value / 100;
if (value >= 0)
{
while (div)
{
Platform::MemoryCopy(it, &DigitPairs[2 * (value - div * 100)], 2);
value = div;
it -= 2;
div = value / 100;
}
Platform::MemoryCopy(it, &DigitPairs[2 * value], 2);
if (value < 10)
it++;
}
else
{
while (div)
{
Platform::MemoryCopy(it, &DigitPairs[-2 * (value - div * 100)], 2);
value = div;
it -= 2;
div = value / 100;
}
Platform::MemoryCopy(it, &DigitPairs[-2 * value], 2);
if (value <= -10)
it--;
*it = '-';
}
return String(it, (int32)(&buf[STRING_UTILS_ITOSTR_BUFFER_SIZE] - it));
}
String StringUtils::ToString(uint32 value)
{
char buf[STRING_UTILS_ITOSTR_BUFFER_SIZE];
char* it = &buf[STRING_UTILS_ITOSTR_BUFFER_SIZE - 2];
int32 div = value / 100;
while (div)
{
Platform::MemoryCopy(it, &DigitPairs[2 * (value - div * 100)], 2);
value = div;
it -= 2;
div = value / 100;
}
Platform::MemoryCopy(it, &DigitPairs[2 * value], 2);
if (value < 10)
it++;
return String((char*)it, (int32)((char*)&buf[STRING_UTILS_ITOSTR_BUFFER_SIZE] - (char*)it));
}
String StringUtils::ToString(uint64 value)
{
char buf[STRING_UTILS_ITOSTR_BUFFER_SIZE];
char* it = &buf[STRING_UTILS_ITOSTR_BUFFER_SIZE - 2];
int64 div = value / 100;
while (div)
{
Platform::MemoryCopy(it, &DigitPairs[2 * (value - div * 100)], 2);
value = div;
it -= 2;
div = value / 100;
}
Platform::MemoryCopy(it, &DigitPairs[2 * value], 2);
if (value < 10)
it++;
return String((char*)it, (int32)((char*)&buf[STRING_UTILS_ITOSTR_BUFFER_SIZE] - (char*)it));
}
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++;
}
const int len = end - str;
return String(str, len);
}
#undef STRING_UTILS_ITOSTR_BUFFER_SIZE