// Copyright (c) 2012-2020 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/Types/StringView.h" #include "Engine/Core/Math/Math.h" #include "Engine/Core/Collections/Array.h" #if PLATFORM_TEXT_IS_CHAR16 #include #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 StringUtils::ConvertUTF82UTF16(const char* from, Char* to, uint32 fromLength, uint32* toLength) { Array unicode; uint32 i = 0; *toLength = 0; while (i < fromLength) { unsigned long uni; uint32 todo; unsigned char ch = from[i++]; if (ch <= 0x7F) { uni = ch; todo = 0; } else if (ch <= 0xBF) { LOG(Error, "Not a UTF-8 string."); 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 { LOG(Error, "Not a UTF-8 string."); return; } for (uint32 j = 0; j < todo; j++) { if (i == fromLength) { LOG(Error, "Not a UTF-8 string."); return; } ch = from[i++]; if (ch < 0x80 || ch > 0xBF) { LOG(Error, "Not a UTF-8 string."); return; } uni <<= 6; uni += ch & 0x3F; } if ((uni >= 0xD800 && uni <= 0xDFFF) || uni > 0x10FFFF) { LOG(Error, "Not a UTF-8 string."); return; } unicode.Add(uni); } // Count chars uint32 length = (uint32)unicode.Count(); for (i = 0; i < length; i++) { if (unicode[i] > 0xFFFF) { length++; } } // Copy chars *toLength = length; for (i = 0; i < length; i++) { unsigned long uni = unicode[i]; if (uni <= 0xFFFF) { to[i] = (Char)uni; } else { uni -= 0x10000; to[i++] += (Char)((uni >> 10) + 0xD800); to[i] += (Char)((uni & 0x3FF) + 0xDC00); } } } 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); } String StringUtils::GetDirectoryName(const String& 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) : String::Empty; } String StringUtils::GetFileName(const String& 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); } String StringUtils::GetFileNameWithoutExtension(const String& path) { String filename = GetFileName(path); const int32 num = filename.FindLast('.'); if (num != -1) { return filename.Substring(0, num); } return filename; } String StringUtils::GetPathWithoutExtension(const String& 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 components; path.Split(TEXT('/'), components); Array 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); } } bool isRooted = path.StartsWith(TEXT('/')); path.Clear(); for (auto& e : stack) path /= e; if (isRooted) path.Insert(0, TEXT("/")); } const char digit_pairs[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; } 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, &digit_pairs[2 * (value - div * 100)], 2); value = div; it -= 2; div = value / 100; } Platform::MemoryCopy(it, &digit_pairs[2 * value], 2); if (value < 10) it++; } else { while (div) { Platform::MemoryCopy(it, &digit_pairs[-2 * (value - div * 100)], 2); value = div; it -= 2; div = value / 100; } Platform::MemoryCopy(it, &digit_pairs[-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, &digit_pairs[2 * (value - div * 100)], 2); value = div; it -= 2; div = value / 100; } Platform::MemoryCopy(it, &digit_pairs[2 * value], 2); if (value < 10) it++; } else { while (div) { Platform::MemoryCopy(it, &digit_pairs[-2 * (value - div * 100)], 2); value = div; it -= 2; div = value / 100; } Platform::MemoryCopy(it, &digit_pairs[-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, &digit_pairs[2 * (value - div * 100)], 2); value = div; it -= 2; div = value / 100; } Platform::MemoryCopy(it, &digit_pairs[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, &digit_pairs[2 * (value - div * 100)], 2); value = div; it -= 2; div = value / 100; } Platform::MemoryCopy(it, &digit_pairs[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); } #undef STRING_UTILS_ITOSTR_BUFFER_SIZE