From 220954cf08126fe30275c8aa542a5c7980832f8d Mon Sep 17 00:00:00 2001 From: Relintai Date: Mon, 15 Aug 2022 22:54:03 +0200 Subject: [PATCH] Backported most of the improvements to String from Godot4. --- core/cowdata.h | 2 + core/io/file_access_zip.cpp | 4 +- core/method_bind.h | 14 +- core/os/os.cpp | 6 +- core/string_buffer.h | 2 +- core/type_info.h | 3 +- core/ustring.cpp | 7532 +++++++++-------- core/ustring.h | 186 +- core/variant_call.cpp | 57 + doc/classes/PoolByteArray.xml | 16 +- doc/classes/String.xml | 40 +- drivers/unix/file_access_unix.cpp | 2 +- drivers/windows/dir_access_windows.cpp | 137 +- drivers/windows/file_access_windows.cpp | 65 +- editor/editor_export.cpp | 5 +- editor/editor_file_dialog.cpp | 7 +- editor/editor_run.cpp | 4 +- editor/plugins/canvas_item_editor_plugin.cpp | 2 +- editor/plugins/spatial_editor_plugin.cpp | 2 +- editor/project_settings_editor.cpp | 2 +- main/tests/test_basis.cpp | 22 +- main/tests/test_main.cpp | 8 +- .../{test_string.cpp => test_string.cpp.old} | 188 +- main/tests/test_transform.cpp | 8 +- modules/database/query_builder.cpp | 2 +- modules/database/table_builder.cpp | 2 +- modules/database_mysql/mysql_database.cpp | 14 +- .../database_sqlite/sqlite3_connection.cpp | 2 +- modules/regex/regex.cpp | 146 +- modules/rtile_map/tile_set.cpp | 4 +- modules/ui_extensions/input_map_editor.cpp | 2 +- .../web/nodes/message_page/message_page.cpp | 8 +- platform/iphone/export/export.cpp | 6 +- platform/iphone/ios.mm | 2 +- platform/iphone/os_iphone.mm | 2 +- platform/osx/export/export.cpp | 10 +- platform/windows/os_windows.cpp | 91 +- platform/windows/windows_terminal_logger.cpp | 5 +- scene/2d/canvas_item.cpp | 4 +- scene/gui/file_dialog.cpp | 7 +- scene/gui/rich_text_label.cpp | 2 +- scene/gui/text_edit.cpp | 8 +- 42 files changed, 4733 insertions(+), 3898 deletions(-) rename main/tests/{test_string.cpp => test_string.cpp.old} (78%) diff --git a/core/cowdata.h b/core/cowdata.h index 7ca422b23..5408fab4f 100644 --- a/core/cowdata.h +++ b/core/cowdata.h @@ -39,6 +39,7 @@ template class Vector; class String; +class Char16String; class CharString; template class VMap; @@ -52,6 +53,7 @@ class CowData { template friend class Vector; friend class String; + friend class Char16String; friend class CharString; template friend class VMap; diff --git a/core/io/file_access_zip.cpp b/core/io/file_access_zip.cpp index cdcd665c8..b33587548 100644 --- a/core/io/file_access_zip.cpp +++ b/core/io/file_access_zip.cpp @@ -146,7 +146,7 @@ unzFile ZipArchive::get_file_handle(String p_file) const { } bool ZipArchive::try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset = 0) { - //printf("opening zip pack %ls, %i, %i\n", p_name.c_str(), p_name.extension().nocasecmp_to("zip"), p_name.extension().nocasecmp_to("pcz")); + //printf("opening zip pack %s, %i, %i\n", p_name.utf8().get_data(), p_name.extension().nocasecmp_to("zip"), p_name.extension().nocasecmp_to("pcz")); // load with offset feature only supported for PCK files ERR_FAIL_COND_V_MSG(p_offset != 0, false, "Invalid PCK data. Note that loading files with a non-zero offset isn't supported with ZIP archives."); @@ -196,7 +196,7 @@ bool ZipArchive::try_open_pack(const String &p_path, bool p_replace_files, uint6 uint8_t md5[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; PackedData::get_singleton()->add_path(p_path, fname, 1, 0, md5, this, p_replace_files); - //printf("packed data add path %ls, %ls\n", p_name.c_str(), fname.c_str()); + //printf("packed data add path %s, %s\n", p_name.utf8().get_data(), fname.utf8().get_data()); if ((i + 1) < gi.number_entry) { unzGoToNextFile(zfile); diff --git a/core/method_bind.h b/core/method_bind.h index 65c8b444f..e7376d074 100644 --- a/core/method_bind.h +++ b/core/method_bind.h @@ -183,18 +183,18 @@ VARIANT_ENUM_CAST(Variant::Type); VARIANT_ENUM_CAST(Variant::Operator); template <> -struct VariantCaster { - static _FORCE_INLINE_ wchar_t cast(const Variant &p_variant) { - return (wchar_t)p_variant.operator int(); +struct VariantCaster { + static _FORCE_INLINE_ char32_t cast(const Variant &p_variant) { + return (char32_t)p_variant.operator int(); } }; #ifdef PTRCALL_ENABLED template <> -struct PtrToArg { - _FORCE_INLINE_ static wchar_t convert(const void *p_ptr) { - return wchar_t(*reinterpret_cast(p_ptr)); +struct PtrToArg { + _FORCE_INLINE_ static char32_t convert(const void *p_ptr) { + return char32_t(*reinterpret_cast(p_ptr)); } - _FORCE_INLINE_ static void encode(wchar_t p_val, const void *p_ptr) { + _FORCE_INLINE_ static void encode(char32_t p_val, const void *p_ptr) { *(int *)p_ptr = p_val; } }; diff --git a/core/os/os.cpp b/core/os/os.cpp index c53b77b35..7b926d78d 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -497,12 +497,12 @@ Error OS::shell_open(String p_uri) { // implement these with the canvas? Error OS::dialog_show(String p_title, String p_description, Vector p_buttons, Object *p_obj, String p_callback) { while (true) { - print("%ls\n--------\n%ls\n", p_title.c_str(), p_description.c_str()); + print("%s\n--------\n%s\n", p_title.utf8().get_data(), p_description.utf8().get_data()); for (int i = 0; i < p_buttons.size(); i++) { if (i > 0) { print(", "); } - print("%i=%ls", i + 1, p_buttons[i].c_str()); + print("%i=%s", i + 1, p_buttons[i].utf8().get_data()); }; print("\n"); String res = get_stdin_string().strip_edges(); @@ -524,7 +524,7 @@ Error OS::dialog_show(String p_title, String p_description, Vector p_but Error OS::dialog_input_text(String p_title, String p_description, String p_partial, Object *p_obj, String p_callback) { ERR_FAIL_COND_V(!p_obj, FAILED); ERR_FAIL_COND_V(p_callback == "", FAILED); - print("%ls\n---------\n%ls\n[%ls]:\n", p_title.c_str(), p_description.c_str(), p_partial.c_str()); + print("%s\n---------\n%s\n[%s]:\n", p_title.utf8().get_data(), p_description.utf8().get_data(), p_partial.utf8().get_data()); String res = get_stdin_string().strip_edges(); bool success = true; diff --git a/core/string_buffer.h b/core/string_buffer.h index 534350a3d..82d3f5c9a 100644 --- a/core/string_buffer.h +++ b/core/string_buffer.h @@ -91,7 +91,7 @@ StringBuffer &StringBuffer::append(CharTyp template StringBuffer &StringBuffer::append(const String &p_string) { - return append(p_string.c_str()); + return append(p_string.get_data()); } template diff --git a/core/type_info.h b/core/type_info.h index 78b4f3b68..f87f6a524 100644 --- a/core/type_info.h +++ b/core/type_info.h @@ -131,7 +131,8 @@ MAKE_TYPE_INFO_WITH_META(uint32_t, Variant::INT, PandemoniumTypeInfo::METADATA_I MAKE_TYPE_INFO_WITH_META(int32_t, Variant::INT, PandemoniumTypeInfo::METADATA_INT_IS_INT32) MAKE_TYPE_INFO_WITH_META(uint64_t, Variant::INT, PandemoniumTypeInfo::METADATA_INT_IS_UINT64) MAKE_TYPE_INFO_WITH_META(int64_t, Variant::INT, PandemoniumTypeInfo::METADATA_INT_IS_INT64) -MAKE_TYPE_INFO(wchar_t, Variant::INT) +MAKE_TYPE_INFO(char16_t, Variant::INT) +MAKE_TYPE_INFO(char32_t, Variant::INT) MAKE_TYPE_INFO_WITH_META(float, Variant::REAL, PandemoniumTypeInfo::METADATA_REAL_IS_FLOAT) MAKE_TYPE_INFO_WITH_META(double, Variant::REAL, PandemoniumTypeInfo::METADATA_REAL_IS_DOUBLE) diff --git a/core/ustring.cpp b/core/ustring.cpp index c716b67a9..685514298 100644 --- a/core/ustring.cpp +++ b/core/ustring.cpp @@ -62,11 +62,38 @@ #define IS_DIGIT(m_d) ((m_d) >= '0' && (m_d) <= '9') #define IS_HEX_DIGIT(m_d) (((m_d) >= '0' && (m_d) <= '9') || ((m_d) >= 'a' && (m_d) <= 'f') || ((m_d) >= 'A' && (m_d) <= 'F')) +#define READING_SIGN 0 +#define READING_INT 1 +#define READING_DEC 2 +#define READING_EXP 3 +#define READING_DONE 4 + const char CharString::_null = 0; +const char16_t Char16String::_null = 0; const CharType String::_null = 0; -bool is_symbol(CharType c) { - return c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t' || c == ' '); +static bool _wildcard_match(const CharType *p_pattern, const CharType *p_string, bool p_case_sensitive) { + switch (*p_pattern) { + case '\0': + return !*p_string; + case '*': + return _wildcard_match(p_pattern + 1, p_string, p_case_sensitive) || (*p_string && _wildcard_match(p_pattern, p_string + 1, p_case_sensitive)); + case '?': + return *p_string && (*p_string != '.') && _wildcard_match(p_pattern + 1, p_string + 1, p_case_sensitive); + default: + + return (p_case_sensitive ? (*p_string == *p_pattern) : (_find_upper(*p_string) == _find_upper(*p_pattern))) && _wildcard_match(p_pattern + 1, p_string + 1, p_case_sensitive); + } +} + +static int _humanize_digits(int p_num) { + if (p_num < 100) { + return 2; + } else if (p_num < 1024) { + return 1; + } else { + return 0; + } } bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end) { @@ -97,7 +124,67 @@ bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end) { } } -/** STRING **/ +/*************************************************************************/ +/* Char16String */ +/*************************************************************************/ + +bool Char16String::operator<(const Char16String &p_right) const { + if (length() == 0) { + return p_right.length() != 0; + } + + return is_str_less(get_data(), p_right.get_data()); +} + +Char16String &Char16String::operator+=(char16_t p_char) { + const int lhs_len = length(); + resize(lhs_len + 2); + + char16_t *dst = ptrw(); + dst[lhs_len] = p_char; + dst[lhs_len + 1] = 0; + + return *this; +} + +void Char16String::operator=(const char16_t *p_cstr) { + copy_from(p_cstr); +} + +const char16_t *Char16String::get_data() const { + if (size()) { + return &operator[](0); + } else { + return u""; + } +} + +void Char16String::copy_from(const char16_t *p_cstr) { + if (!p_cstr) { + resize(0); + return; + } + + const char16_t *s = p_cstr; + for (; *s; s++) { + } + size_t len = s - p_cstr; + + if (len == 0) { + resize(0); + return; + } + + Error err = resize(++len); // include terminating null char + + ERR_FAIL_COND_MSG(err != OK, "Failed to copy char16_t string."); + + memcpy(ptrw(), p_cstr, len * sizeof(char16_t)); +} + +/*************************************************************************/ +/* CharString */ +/*************************************************************************/ bool CharString::operator<(const CharString &p_right) const { if (length() == 0) { @@ -118,6 +205,10 @@ CharString &CharString::operator+=(char p_char) { return *this; } +void CharString::operator=(const char *p_cstr) { + copy_from(p_cstr); +} + const char *CharString::get_data() const { if (size()) { return &operator[](0); @@ -126,11 +217,6 @@ const char *CharString::get_data() const { } } -CharString &CharString::operator=(const char *p_cstr) { - copy_from(p_cstr); - return *this; -} - void CharString::copy_from(const char *p_cstr) { if (!p_cstr) { resize(0); @@ -144,77 +230,19 @@ void CharString::copy_from(const char *p_cstr) { return; } - resize(len + 1); // include terminating null char + Error err = resize(++len); // include terminating null char - strcpy(ptrw(), p_cstr); + ERR_FAIL_COND_MSG(err != OK, "Failed to copy C-string."); + + memcpy(ptrw(), p_cstr, len); } -Error String::parse_url(String &r_scheme, String &r_host, int &r_port, String &r_path) const { - // Splits the URL into scheme, host, port, path. Strip credentials when present. - String base = *this; - r_scheme = ""; - r_host = ""; - r_port = 0; - r_path = ""; - int pos = base.find("://"); - // Scheme - if (pos != -1) { - r_scheme = base.substr(0, pos + 3).to_lower(); - base = base.substr(pos + 3, base.length() - pos - 3); - } - pos = base.find("/"); - // Path - if (pos != -1) { - r_path = base.substr(pos, base.length() - pos); - base = base.substr(0, pos); - } - // Host - pos = base.find("@"); - if (pos != -1) { - // Strip credentials - base = base.substr(pos + 1, base.length() - pos - 1); - } - if (base.begins_with("[")) { - // Literal IPv6 - pos = base.rfind("]"); - if (pos == -1) { - return ERR_INVALID_PARAMETER; - } - r_host = base.substr(1, pos - 1); - base = base.substr(pos + 1, base.length() - pos - 1); - } else { - // Anything else - if (base.get_slice_count(":") > 2) { - return ERR_INVALID_PARAMETER; - } - pos = base.rfind(":"); - if (pos == -1) { - r_host = base; - base = ""; - } else { - r_host = base.substr(0, pos); - base = base.substr(pos, base.length() - pos); - } - } - if (r_host.empty()) { - return ERR_INVALID_PARAMETER; - } - r_host = r_host.to_lower(); - // Port - if (base.begins_with(":")) { - base = base.substr(1, base.length() - 1); - if (!base.is_valid_integer()) { - return ERR_INVALID_PARAMETER; - } - r_port = base.to_int(); - if (r_port < 1 || r_port > 65535) { - return ERR_INVALID_PARAMETER; - } - } - return OK; -} +/*************************************************************************/ +/* String */ +/*************************************************************************/ void String::copy_from(const char *p_cstr) { + // copy Latin-1 encoded c-string directly if (!p_cstr) { resize(0); return; @@ -232,10 +260,109 @@ void String::copy_from(const char *p_cstr) { CharType *dst = this->ptrw(); for (size_t i = 0; i <= len; i++) { - dst[i] = p_cstr[i]; + uint8_t c = p_cstr[i] >= 0 ? p_cstr[i] : uint8_t(256 + p_cstr[i]); + if (c == 0 && i < len) { + print_unicode_error("NUL character", true); + dst[i] = 0x20; + } else { + dst[i] = c; + } } } +void String::copy_from(const char *p_cstr, const int p_clip_to) { + // copy Latin-1 encoded c-string directly + if (!p_cstr) { + resize(0); + return; + } + + int len = 0; + const char *ptr = p_cstr; + while ((p_clip_to < 0 || len < p_clip_to) && *(ptr++) != 0) { + len++; + } + + if (len == 0) { + resize(0); + return; + } + + resize(len + 1); // include 0 + + CharType *dst = this->ptrw(); + + for (int i = 0; i < len; i++) { + uint8_t c = p_cstr[i] >= 0 ? p_cstr[i] : uint8_t(256 + p_cstr[i]); + if (c == 0) { + print_unicode_error("NUL character", true); + dst[i] = 0x20; + } else { + dst[i] = c; + } + } + dst[len] = 0; +} + +void String::copy_from(const wchar_t *p_cstr) { +#ifdef WINDOWS_ENABLED + // wchar_t is 16-bit, parse as UTF-16 + parse_utf16((const char16_t *)p_cstr); +#else + // wchar_t is 32-bit, copy directly + copy_from((const CharType *)p_cstr); +#endif +} + +void String::copy_from(const wchar_t *p_cstr, const int p_clip_to) { +#ifdef WINDOWS_ENABLED + // wchar_t is 16-bit, parse as UTF-16 + parse_utf16((const char16_t *)p_cstr, p_clip_to); +#else + // wchar_t is 32-bit, copy directly + copy_from((const CharType *)p_cstr, p_clip_to); +#endif +} + +void String::copy_from(const CharType &p_char) { + if (p_char == 0) { + print_unicode_error("NUL character", true); + return; + } + if ((p_char & 0xfffff800) == 0xd800) { + print_unicode_error(vformat("Unpaired surrogate (%x)", (uint32_t)p_char)); + } + if (p_char > 0x10ffff) { + print_unicode_error(vformat("Invalid unicode codepoint (%x)", (uint32_t)p_char)); + } + + resize(2); + + CharType *dst = ptrw(); + dst[0] = p_char; + dst[1] = 0; +} + +void String::copy_from(const CharType *p_cstr) { + if (!p_cstr) { + resize(0); + return; + } + + int len = 0; + const CharType *ptr = p_cstr; + while (*(ptr++) != 0) { + len++; + } + + if (len == 0) { + resize(0); + return; + } + + copy_from_unchecked(p_cstr, len); +} + void String::copy_from(const CharType *p_cstr, const int p_clip_to) { if (!p_cstr) { resize(0); @@ -257,22 +384,28 @@ void String::copy_from(const CharType *p_cstr, const int p_clip_to) { } // assumes the following have already been validated: -// p_char != NULL +// p_char != nullptr // p_length > 0 // p_length <= p_char strlen void String::copy_from_unchecked(const CharType *p_char, const int p_length) { resize(p_length + 1); - CharType *dst = ptrw(); - memcpy(dst, p_char, p_length * sizeof(CharType)); dst[p_length] = 0; -} -void String::copy_from(const CharType &p_char) { - resize(2); - CharType *dst = ptrw(); - dst[0] = p_char; - dst[1] = 0; + for (int i = 0; i < p_length; i++) { + if (p_char[i] == 0) { + print_unicode_error("NUL character", true); + dst[i] = 0x20; + continue; + } + if ((p_char[i] & 0xfffff800) == 0xd800) { + print_unicode_error(vformat("Unpaired surrogate (%x)", (uint32_t)p_char[i])); + } + if (p_char[i] > 0x10ffff) { + print_unicode_error(vformat("Invalid unicode codepoint (%x)", (uint32_t)p_char[i])); + } + dst[i] = p_char[i]; + } } bool String::operator==(const String &p_str) const { @@ -285,8 +418,8 @@ bool String::operator==(const String &p_str) const { int l = length(); - const CharType *src = c_str(); - const CharType *dst = p_str.c_str(); + const CharType *src = get_data(); + const CharType *dst = p_str.get_data(); /* Compare char by char */ for (int i = 0; i < l; i++) { @@ -299,7 +432,7 @@ bool String::operator==(const String &p_str) const { } bool String::operator!=(const String &p_str) const { - return !(*this == p_str); + return !((*this == p_str)); } String String::operator+(const String &p_str) const { @@ -308,6 +441,14 @@ String String::operator+(const String &p_str) const { return res; } +/* +String String::operator+(CharType p_char) const { + String res = *this; + res += p_char; + return res; +} +*/ + String &String::operator+=(const String &p_str) { const int lhs_len = length(); if (lhs_len == 0) { @@ -322,7 +463,7 @@ String &String::operator+=(const String &p_str) { resize(lhs_len + rhs_len + 1); - const CharType *src = p_str.c_str(); + const CharType *src = p_str.get_data(); CharType *dst = ptrw() + lhs_len; memcpy(dst, src, (rhs_len + 1) * sizeof(CharType)); @@ -330,15 +471,20 @@ String &String::operator+=(const String &p_str) { return *this; } -String &String::operator+=(const CharType *p_str) { - *this += String(p_str); - return *this; -} - String &String::operator+=(CharType p_char) { + if (p_char == 0) { + print_unicode_error("NUL character", true); + return *this; + } + if ((p_char & 0xfffff800) == 0xd800) { + print_unicode_error(vformat("Unpaired surrogate (%x)", (uint32_t)p_char)); + } + if (p_char > 0x10ffff) { + print_unicode_error(vformat("Invalid unicode codepoint (%x)", (uint32_t)p_char)); + } + const int lhs_len = length(); resize(lhs_len + 2); - CharType *dst = ptrw(); dst[lhs_len] = p_char; dst[lhs_len + 1] = 0; @@ -351,20 +497,42 @@ String &String::operator+=(const char *p_str) { return *this; } - const size_t rhs_len = strlen(p_str); const int lhs_len = length(); + const size_t rhs_len = strlen(p_str); resize(lhs_len + rhs_len + 1); CharType *dst = ptrw() + lhs_len; for (size_t i = 0; i <= rhs_len; i++) { - dst[i] = p_str[i]; + uint8_t c = p_str[i] >= 0 ? p_str[i] : uint8_t(256 + p_str[i]); + if (c == 0 && i < rhs_len) { + print_unicode_error("NUL character", true); + dst[i] = 0x20; + } else { + dst[i] = c; + } } return *this; } +String &String::operator+=(const wchar_t *p_str) { +#ifdef WINDOWS_ENABLED + // wchar_t is 16-bit + *this += String::utf16((const char16_t *)p_str); +#else + // wchar_t is 32-bit + *this += String((const CharType *)p_str); +#endif + return *this; +} + +String &String::operator+=(const CharType *p_str) { + *this += String(p_str); + return *this; +} + void String::operator=(const char *p_str) { copy_from(p_str); } @@ -373,6 +541,79 @@ void String::operator=(const CharType *p_str) { copy_from(p_str); } +void String::operator=(const wchar_t *p_str) { + copy_from(p_str); +} + +bool String::operator==(const char *p_str) const { + // compare Latin-1 encoded c-string + int len = 0; + const char *aux = p_str; + + while (*(aux++) != 0) { + len++; + } + + if (length() != len) { + return false; + } + if (empty()) { + return true; + } + + int l = length(); + + const CharType *dst = get_data(); + + // Compare char by char + for (int i = 0; i < l; i++) { + if ((CharType)p_str[i] != dst[i]) { + return false; + } + } + + return true; +} + +bool String::operator==(const wchar_t *p_str) const { +#ifdef WINDOWS_ENABLED + // wchar_t is 16-bit, parse as UTF-16 + return *this == String::utf16((const char16_t *)p_str); +#else + // wchar_t is 32-bit, compare char by char + return *this == (const CharType *)p_str; +#endif +} + +bool String::operator==(const CharType *p_str) const { + int len = 0; + const CharType *aux = p_str; + + while (*(aux++) != 0) { + len++; + } + + if (length() != len) { + return false; + } + if (empty()) { + return true; + } + + int l = length(); + + const CharType *dst = get_data(); + + /* Compare char by char */ + for (int i = 0; i < l; i++) { + if (p_str[i] != dst[i]) { + return false; + } + } + + return true; +} + bool String::operator==(const StrRange &p_str_range) const { int len = p_str_range.len; @@ -396,72 +637,45 @@ bool String::operator==(const StrRange &p_str_range) const { return true; } -bool String::operator==(const char *p_str) const { - int len = 0; - const char *aux = p_str; - - while (*(aux++) != 0) { - len++; - } - - if (length() != len) { - return false; - } - if (empty()) { - return true; - } - - int l = length(); - - const CharType *dst = c_str(); - - /* Compare char by char */ - for (int i = 0; i < l; i++) { - if (p_str[i] != dst[i]) { - return false; - } - } - - return true; -} - -bool String::operator==(const CharType *p_str) const { - int len = 0; - const CharType *aux = p_str; - - while (*(aux++) != 0) { - len++; - } - - if (length() != len) { - return false; - } - if (empty()) { - return true; - } - - int l = length(); - - const CharType *dst = c_str(); - - /* Compare char by char */ - for (int i = 0; i < l; i++) { - if (p_str[i] != dst[i]) { - return false; - } - } - - return true; -} - bool String::operator!=(const char *p_str) const { return (!(*this == p_str)); } +bool String::operator!=(const wchar_t *p_str) const { + return (!(*this == p_str)); +} + bool String::operator!=(const CharType *p_str) const { return (!(*this == p_str)); } +bool String::operator<(const char *p_str) const { + if (empty() && p_str[0] == 0) { + return false; + } + if (empty()) { + return true; + } + return is_str_less(get_data(), p_str); +} + +bool String::operator<(const wchar_t *p_str) const { + if (empty() && p_str[0] == 0) { + return false; + } + if (empty()) { + return true; + } + +#ifdef WINDOWS_ENABLED + // wchar_t is 16-bit + return is_str_less(get_data(), String::utf16((const char16_t *)p_str).get_data()); +#else + // wchar_t is 32-bit + return is_str_less(get_data(), (const CharType *)p_str); +#endif +} + bool String::operator<(const CharType *p_str) const { if (empty() && p_str[0] == 0) { return false; @@ -470,26 +684,23 @@ bool String::operator<(const CharType *p_str) const { return true; } - return is_str_less(c_str(), p_str); -} - -bool String::operator<=(const String &p_str) const { - return (*this < p_str) || (*this == p_str); -} - -bool String::operator<(const char *p_str) const { - if (empty() && p_str[0] == 0) { - return false; - } - if (empty()) { - return true; - } - - return is_str_less(c_str(), p_str); + return is_str_less(get_data(), p_str); } bool String::operator<(const String &p_str) const { - return operator<(p_str.c_str()); + return operator<(p_str.get_data()); +} + +bool String::operator<=(const String &p_str) const { + return !(p_str < *this); +} + +bool String::operator>(const String &p_str) const { + return p_str < *this; +} + +bool String::operator>=(const String &p_str) const { + return !(*this < p_str); } signed char String::nocasecmp_to(const String &p_str) const { @@ -503,8 +714,8 @@ signed char String::nocasecmp_to(const String &p_str) const { return 1; } - const CharType *that_str = p_str.c_str(); - const CharType *this_str = c_str(); + const CharType *that_str = p_str.get_data(); + const CharType *this_str = get_data(); while (true) { if (*that_str == 0 && *this_str == 0) { @@ -535,8 +746,8 @@ signed char String::casecmp_to(const String &p_str) const { return 1; } - const CharType *that_str = p_str.c_str(); - const CharType *this_str = c_str(); + const CharType *that_str = p_str.get_data(); + const CharType *this_str = get_data(); while (true) { if (*that_str == 0 && *this_str == 0) { @@ -557,8 +768,8 @@ signed char String::casecmp_to(const String &p_str) const { } signed char String::naturalnocasecmp_to(const String &p_str) const { - const CharType *this_str = c_str(); - const CharType *that_str = p_str.c_str(); + const CharType *this_str = get_data(); + const CharType *that_str = p_str.get_data(); if (this_str && that_str) { while (*this_str == '.' || *that_str == '.') { @@ -579,8 +790,8 @@ signed char String::naturalnocasecmp_to(const String &p_str) const { while (*this_str) { if (!*that_str) { return 1; - } else if (IS_DIGIT(*this_str)) { - if (!IS_DIGIT(*that_str)) { + } else if (is_digit(*this_str)) { + if (!is_digit(*that_str)) { return -1; } @@ -589,10 +800,10 @@ signed char String::naturalnocasecmp_to(const String &p_str) const { const CharType *that_substr = that_str; // Compare lengths of both numerical sequences, ignoring leading zeros - while (IS_DIGIT(*this_str)) { + while (is_digit(*this_str)) { this_str++; } - while (IS_DIGIT(*that_str)) { + while (is_digit(*that_str)) { that_str++; } while (*this_substr == '0') { @@ -620,7 +831,7 @@ signed char String::naturalnocasecmp_to(const String &p_str) const { this_substr++; that_substr++; } - } else if (IS_DIGIT(*that_str)) { + } else if (is_digit(*that_str)) { return 1; } else { if (_find_upper(*this_str) < _find_upper(*that_str)) { //more than @@ -641,8 +852,1579 @@ signed char String::naturalnocasecmp_to(const String &p_str) const { return 0; } -void String::erase(int p_pos, int p_chars) { - *this = left(p_pos) + substr(p_pos + p_chars, length() - ((p_pos + p_chars))); +const CharType *String::get_data() const { + static const CharType zero = 0; + return size() ? &operator[](0) : &zero; +} + +bool String::is_valid_string() const { + int l = length(); + const CharType *src = get_data(); + bool valid = true; + for (int i = 0; i < l; i++) { + valid = valid && (src[i] < 0xd800 || (src[i] > 0xdfff && src[i] <= 0x10ffff)); + } + return valid; +} + +void String::print_unicode_error(const String &p_message, bool p_critical) const { + if (p_critical) { + print_error(vformat("Unicode parsing error, some characters were replaced with spaces: %s", p_message)); + } else { + print_error(vformat("Unicode parsing error: %s", p_message)); + } +} + +/* complex helpers */ + +String String::substr(int p_from, int p_chars) const { + if (p_chars == -1) { + p_chars = length() - p_from; + } + + if (empty() || p_from < 0 || p_from >= length() || p_chars <= 0) { + return ""; + } + + if ((p_from + p_chars) > length()) { + p_chars = length() - p_from; + } + + if (p_from == 0 && p_chars >= length()) { + return String(*this); + } + + String s = String(); + s.copy_from_unchecked(&get_data()[p_from], p_chars); + return s; +} + +String String::substr_index(const int start_index, const int end_index) const { + int s = length(); + + if (start_index < 0 || start_index >= s || end_index < 0 || start_index >= s) { + return ""; + } + + if (start_index > end_index) { + return ""; + } + + return substr(start_index, end_index - start_index); +} + +int String::find(const String &p_str, int p_from) const { + if (p_from < 0) { + return -1; + } + + const int src_len = p_str.length(); + + const int len = length(); + + if (src_len == 0 || len == 0) { + return -1; // won't find anything! + } + + const CharType *src = get_data(); + const CharType *str = p_str.get_data(); + + for (int i = p_from; i <= (len - src_len); i++) { + bool found = true; + for (int j = 0; j < src_len; j++) { + int read_pos = i + j; + + if (read_pos >= len) { + ERR_PRINT("read_pos>=len"); + return -1; + }; + + if (src[read_pos] != str[j]) { + found = false; + break; + } + } + + if (found) { + return i; + } + } + + return -1; +} + +int String::find(const char *p_str, int p_from) const { + if (p_from < 0) { + return -1; + } + + const int len = length(); + + if (len == 0) { + return -1; // won't find anything! + } + + const CharType *src = get_data(); + + int src_len = 0; + while (p_str[src_len] != '\0') { + src_len++; + } + + if (src_len == 1) { + const CharType needle = p_str[0]; + + for (int i = p_from; i < len; i++) { + if (src[i] == needle) { + return i; + } + } + + } else { + for (int i = p_from; i <= (len - src_len); i++) { + bool found = true; + for (int j = 0; j < src_len; j++) { + int read_pos = i + j; + + if (read_pos >= len) { + ERR_PRINT("read_pos>=len"); + return -1; + }; + + if (src[read_pos] != (CharType)p_str[j]) { + found = false; + break; + } + } + + if (found) { + return i; + } + } + } + + return -1; +} + +int String::find_char(const CharType &p_char, int p_from) const { + return _cowdata.find(p_char, p_from); +} + +int String::find_last(const String &p_str) const { + return rfind(p_str); +} + +int String::findn(const String &p_str, int p_from) const { + if (p_from < 0) { + return -1; + } + + int src_len = p_str.length(); + + if (src_len == 0 || length() == 0) { + return -1; // won't find anything! + } + + const CharType *srcd = get_data(); + + for (int i = p_from; i <= (length() - src_len); i++) { + bool found = true; + for (int j = 0; j < src_len; j++) { + int read_pos = i + j; + + if (read_pos >= length()) { + ERR_PRINT("read_pos>=length()"); + return -1; + }; + + CharType src = _find_lower(srcd[read_pos]); + CharType dst = _find_lower(p_str[j]); + + if (src != dst) { + found = false; + break; + } + } + + if (found) { + return i; + } + } + + return -1; +} + +int String::rfind(const String &p_str, int p_from) const { + // establish a limit + int limit = length() - p_str.length(); + if (limit < 0) { + return -1; + } + + // establish a starting point + if (p_from < 0) { + p_from = limit; + } else if (p_from > limit) { + p_from = limit; + } + + int src_len = p_str.length(); + int len = length(); + + if (src_len == 0 || len == 0) { + return -1; // won't find anything! + } + + const CharType *src = get_data(); + + for (int i = p_from; i >= 0; i--) { + bool found = true; + for (int j = 0; j < src_len; j++) { + int read_pos = i + j; + + if (read_pos >= len) { + ERR_PRINT("read_pos>=len"); + return -1; + }; + + if (src[read_pos] != p_str[j]) { + found = false; + break; + } + } + + if (found) { + return i; + } + } + + return -1; +} + +int String::rfindn(const String &p_str, int p_from) const { + // establish a limit + int limit = length() - p_str.length(); + if (limit < 0) { + return -1; + } + + // establish a starting point + if (p_from < 0) { + p_from = limit; + } else if (p_from > limit) { + p_from = limit; + } + + int src_len = p_str.length(); + int len = length(); + + if (src_len == 0 || len == 0) { + return -1; // won't find anything! + } + + const CharType *src = get_data(); + + for (int i = p_from; i >= 0; i--) { + bool found = true; + for (int j = 0; j < src_len; j++) { + int read_pos = i + j; + + if (read_pos >= len) { + ERR_PRINT("read_pos>=len"); + return -1; + }; + + CharType srcc = _find_lower(src[read_pos]); + CharType dstc = _find_lower(p_str[j]); + + if (srcc != dstc) { + found = false; + break; + } + } + + if (found) { + return i; + } + } + + return -1; +} + +int String::findmk(const Vector &p_keys, int p_from, int *r_key) const { + if (p_from < 0) { + return -1; + } + if (p_keys.size() == 0) { + return -1; + } + + //int src_len=p_str.length(); + const String *keys = &p_keys[0]; + int key_count = p_keys.size(); + int len = length(); + + if (len == 0) { + return -1; // won't find anything! + } + + const CharType *src = get_data(); + + for (int i = p_from; i < len; i++) { + bool found = true; + for (int k = 0; k < key_count; k++) { + found = true; + if (r_key) { + *r_key = k; + } + const CharType *cmp = keys[k].get_data(); + int l = keys[k].length(); + + for (int j = 0; j < l; j++) { + int read_pos = i + j; + + if (read_pos >= len) { + found = false; + break; + }; + + if (src[read_pos] != cmp[j]) { + found = false; + break; + } + } + if (found) { + break; + } + } + + if (found) { + return i; + } + } + + return -1; +} + +int String::find_first_difference_index(const String &p_str) const { + const int olen = p_str.length(); + const int len = length(); + const int c = len < olen ? len : olen; + + const CharType *p = get_data(); + const CharType *op = p_str.get_data(); + + for (int i = 0; i < c; ++i) { + if (p[i] != op[i]) { + return i; + } + } + + return c; +} + +bool String::is_word_at(const int index, const char *p_str) const { + int size = length(); + + ERR_FAIL_INDEX_V(index, size, false); + + int i = 0; + + while (p_str[i] != '\0') { + int iind = index + i; + + if (iind >= size) { + return false; + } + + if (operator[](iind) != p_str[i]) { + return false; + } + + ++i; + } + + return true; +} +bool String::is_word_at(const int index, const String &p_str) const { + int size = length(); + + ERR_FAIL_INDEX_V(index, size, false); + + if (index + p_str.length() >= size) { + return false; + } + + for (int i = 0; i < p_str.length(); ++i) { + int iind = index + i; + + if (operator[](iind) != p_str[i]) { + return false; + } + } + + return true; +} + +bool String::match(const String &p_wildcard) const { + if (!p_wildcard.length() || !length()) { + return false; + } + + return _wildcard_match(p_wildcard.get_data(), get_data(), true); +} + +bool String::matchn(const String &p_wildcard) const { + if (!p_wildcard.length() || !length()) { + return false; + } + return _wildcard_match(p_wildcard.get_data(), get_data(), false); +} + +bool String::begins_with(const String &p_string) const { + int l = p_string.length(); + if (l > length()) { + return false; + } + + if (l == 0) { + return true; + } + + const CharType *p = &p_string[0]; + const CharType *s = &operator[](0); + + for (int i = 0; i < l; i++) { + if (p[i] != s[i]) { + return false; + } + } + + return true; +} + +bool String::begins_with(const char *p_string) const { + int l = length(); + if (l == 0 || !p_string) { + return false; + } + + const CharType *str = &operator[](0); + int i = 0; + + while (*p_string && i < l) { + if (*p_string != str[i]) { + return false; + } + i++; + p_string++; + } + + return *p_string == 0; +} + +bool String::ends_with(const String &p_string) const { + int l = p_string.length(); + if (l > length()) { + return false; + } + + if (l == 0) { + return true; + } + + const CharType *p = &p_string[0]; + const CharType *s = &operator[](length() - l); + + for (int i = 0; i < l; i++) { + if (p[i] != s[i]) { + return false; + } + } + + return true; +} + +bool String::is_enclosed_in(const String &p_string) const { + return begins_with(p_string) && ends_with(p_string); +} + +bool String::is_subsequence_of(const String &p_string) const { + return _base_is_subsequence_of(p_string, false); +} + +bool String::is_subsequence_ofi(const String &p_string) const { + return _base_is_subsequence_of(p_string, true); +} + +bool String::is_quoted() const { + return is_enclosed_in("\"") || is_enclosed_in("'"); +} + +Vector String::bigrams() const { + int n_pairs = length() - 1; + Vector b; + if (n_pairs <= 0) { + return b; + } + b.resize(n_pairs); + for (int i = 0; i < n_pairs; i++) { + b.write[i] = substr(i, 2); + } + return b; +} + +// Similarity according to Sorensen-Dice coefficient +float String::similarity(const String &p_string) const { + if (operator==(p_string)) { + // Equal strings are totally similar + return 1.0f; + } + if (length() < 2 || p_string.length() < 2) { + // No way to calculate similarity without a single bigram + return 0.0f; + } + + Vector src_bigrams = bigrams(); + Vector tgt_bigrams = p_string.bigrams(); + + int src_size = src_bigrams.size(); + int tgt_size = tgt_bigrams.size(); + + float sum = src_size + tgt_size; + float inter = 0; + for (int i = 0; i < src_size; i++) { + for (int j = 0; j < tgt_size; j++) { + if (src_bigrams[i] == tgt_bigrams[j]) { + inter++; + break; + } + } + } + + return (2.0f * inter) / sum; +} + +String String::format(const Variant &values, String placeholder) const { + String new_string = String(this->ptr()); + + if (values.get_type() == Variant::ARRAY) { + Array values_arr = values; + + for (int i = 0; i < values_arr.size(); i++) { + String i_as_str = String::num_int64(i); + + if (values_arr[i].get_type() == Variant::ARRAY) { //Array in Array structure [["name","RobotGuy"],[0,"pandemonium"],["strength",9000.91]] + Array value_arr = values_arr[i]; + + if (value_arr.size() == 2) { + Variant v_key = value_arr[0]; + String key = v_key; + + Variant v_val = value_arr[1]; + String val = v_val; + + new_string = new_string.replace(placeholder.replace("_", key), val); + } else { + ERR_PRINT(String("STRING.format Inner Array size != 2 ").ascii().get_data()); + } + } else { //Array structure ["RobotGuy","Logis","rookie"] + Variant v_val = values_arr[i]; + String val = v_val; + + if (placeholder.find("_") > -1) { + new_string = new_string.replace(placeholder.replace("_", i_as_str), val); + } else { + new_string = new_string.replace_first(placeholder, val); + } + } + } + } else if (values.get_type() == Variant::DICTIONARY) { + Dictionary d = values; + List keys; + d.get_key_list(&keys); + + for (List::Element *E = keys.front(); E; E = E->next()) { + String key = E->get(); + String val = d[E->get()]; + + new_string = new_string.replace(placeholder.replace("_", key), val); + } + } else { + ERR_PRINT(String("Invalid type: use Array or Dictionary.").ascii().get_data()); + } + + return new_string; +} + +String String::replace_first(const String &p_key, const String &p_with) const { + int pos = find(p_key); + if (pos >= 0) { + return substr(0, pos) + p_with + substr(pos + p_key.length(), length()); + } + + return *this; +} + +String String::replace(const String &p_key, const String &p_with) const { + String new_string; + int search_from = 0; + int result = 0; + + while ((result = find(p_key, search_from)) >= 0) { + new_string += substr(search_from, result - search_from); + new_string += p_with; + search_from = result + p_key.length(); + } + + if (search_from == 0) { + return *this; + } + + new_string += substr(search_from, length() - search_from); + + return new_string; +} + +String String::replace(const char *p_key, const char *p_with) const { + String new_string; + int search_from = 0; + int result = 0; + + while ((result = find(p_key, search_from)) >= 0) { + new_string += substr(search_from, result - search_from); + new_string += p_with; + int k = 0; + while (p_key[k] != '\0') { + k++; + } + search_from = result + k; + } + + if (search_from == 0) { + return *this; + } + + new_string += substr(search_from, length() - search_from); + + return new_string; +} + +String String::replacen(const String &p_key, const String &p_with) const { + String new_string; + int search_from = 0; + int result = 0; + + while ((result = findn(p_key, search_from)) >= 0) { + new_string += substr(search_from, result - search_from); + new_string += p_with; + search_from = result + p_key.length(); + } + + if (search_from == 0) { + return *this; + } + + new_string += substr(search_from, length() - search_from); + return new_string; +} + +String String::newline_to_br() const { + String r = replace("\r\n", "
"); + return r.replace("\n", "
"); +} + +String String::repeat(int p_count) const { + ERR_FAIL_COND_V_MSG(p_count < 0, "", "Parameter count should be a positive number."); + + String new_string; + const CharType *src = this->get_data(); + + new_string.resize(length() * p_count + 1); + new_string[length() * p_count] = 0; + + for (int i = 0; i < p_count; i++) { + for (int j = 0; j < length(); j++) { + new_string[i * length() + j] = src[j]; + } + } + + return new_string; +} + +String String::insert(int p_at_pos, const String &p_string) const { + if (p_at_pos < 0) { + return *this; + } + + if (p_at_pos > length()) { + p_at_pos = length(); + } + + String pre; + if (p_at_pos > 0) { + pre = substr(0, p_at_pos); + } + + String post; + if (p_at_pos < length()) { + post = substr(p_at_pos, length() - p_at_pos); + } + + return pre + p_string + post; +} + +String String::pad_decimals(int p_digits) const { + String s = *this; + int c = s.find("."); + + if (c == -1) { + if (p_digits <= 0) { + return s; + } + s += "."; + c = s.length() - 1; + } else { + if (p_digits <= 0) { + return s.substr(0, c); + } + } + + if (s.length() - (c + 1) > p_digits) { + s = s.substr(0, c + p_digits + 1); + } else { + while (s.length() - (c + 1) < p_digits) { + s += "0"; + } + } + return s; +} + +String String::pad_zeros(int p_digits) const { + String s = *this; + int end = s.find("."); + + if (end == -1) { + end = s.length(); + } + + if (end == 0) { + return s; + } + + int begin = 0; + + while (begin < end && (s[begin] < '0' || s[begin] > '9')) { + begin++; + } + + if (begin >= end) { + return s; + } + + while (end - begin < p_digits) { + s = s.insert(begin, "0"); + end++; + } + + return s; +} + +String String::trim_prefix(const String &p_prefix) const { + String s = *this; + if (s.begins_with(p_prefix)) { + return s.substr(p_prefix.length(), s.length() - p_prefix.length()); + } + return s; +} + +String String::trim_suffix(const String &p_suffix) const { + String s = *this; + if (s.ends_with(p_suffix)) { + return s.substr(0, s.length() - p_suffix.length()); + } + return s; +} + +// Left-pad with a character. +String String::lpad(int min_length, const String &character) const { + String s = *this; + int padding = min_length - s.length(); + if (padding > 0) { + for (int i = 0; i < padding; i++) { + s = character + s; + } + } + + return s; +} + +// Right-pad with a character. +String String::rpad(int min_length, const String &character) const { + String s = *this; + int padding = min_length - s.length(); + if (padding > 0) { + for (int i = 0; i < padding; i++) { + s = s + character; + } + } + + return s; +} + +// sprintf is implemented in GDScript via: +// "fish %s pie" % "frog" +// "fish %s %d pie" % ["frog", 12] +// In case of an error, the string returned is the error description and "error" is true. +String String::sprintf(const Array &values, bool *error) const { + String formatted; + CharType *self = (CharType *)get_data(); + bool in_format = false; + int value_index = 0; + int min_chars = 0; + int min_decimals = 0; + bool in_decimals = false; + bool pad_with_zeros = false; + bool left_justified = false; + bool show_sign = false; + + if (error) { + *error = true; + } + + for (; *self; self++) { + const CharType c = *self; + + if (in_format) { // We have % - let's see what else we get. + switch (c) { + case '%': { // Replace %% with % + formatted += chr(c); + in_format = false; + break; + } + case 'd': // Integer (signed) + case 'o': // Octal + case 'x': // Hexadecimal (lowercase) + case 'X': { // Hexadecimal (uppercase) + if (value_index >= values.size()) { + return "not enough arguments for format string"; + } + + if (!values[value_index].is_num()) { + return "a number is required"; + } + + int64_t value = values[value_index]; + int base = 16; + bool capitalize = false; + switch (c) { + case 'd': + base = 10; + break; + case 'o': + base = 8; + break; + case 'x': + break; + case 'X': + base = 16; + capitalize = true; + break; + } + // Get basic number. + String str = String::num_int64(ABS(value), base, capitalize); + int number_len = str.length(); + + // Padding. + int pad_chars_count = (value < 0 || show_sign) ? min_chars - 1 : min_chars; + String pad_char = pad_with_zeros ? String("0") : String(" "); + if (left_justified) { + str = str.rpad(pad_chars_count, pad_char); + } else { + str = str.lpad(pad_chars_count, pad_char); + } + + // Sign. + if (show_sign || value < 0) { + String sign_char = value < 0 ? "-" : "+"; + if (left_justified) { + str = str.insert(0, sign_char); + } else { + str = str.insert(pad_with_zeros ? 0 : str.length() - number_len, sign_char); + } + } + + formatted += str; + ++value_index; + in_format = false; + + break; + } + case 'f': { // Float + if (value_index >= values.size()) { + return "not enough arguments for format string"; + } + + if (!values[value_index].is_num()) { + return "a number is required"; + } + + double value = values[value_index]; + bool is_negative = (value < 0); + String str = String::num(ABS(value), min_decimals); + + // Pad decimals out. + str = str.pad_decimals(min_decimals); + + int initial_len = str.length(); + + // Padding. Leave room for sign later if required. + int pad_chars_count = (is_negative || show_sign) ? min_chars - 1 : min_chars; + String pad_char = pad_with_zeros ? String("0") : String(" "); + if (left_justified) { + str = str.rpad(pad_chars_count, pad_char); + } else { + str = str.lpad(pad_chars_count, pad_char); + } + + // Add sign if needed. + if (show_sign || is_negative) { + String sign_char = is_negative ? "-" : "+"; + if (left_justified) { + str = str.insert(0, sign_char); + } else { + str = str.insert(pad_with_zeros ? 0 : str.length() - initial_len, sign_char); + } + } + + formatted += str; + ++value_index; + in_format = false; + break; + } + case 's': { // String + if (value_index >= values.size()) { + return "not enough arguments for format string"; + } + + String str = values[value_index]; + // Padding. + if (left_justified) { + str = str.rpad(min_chars); + } else { + str = str.lpad(min_chars); + } + + formatted += str; + ++value_index; + in_format = false; + break; + } + case 'c': { + if (value_index >= values.size()) { + return "not enough arguments for format string"; + } + + // Convert to character. + String str; + if (values[value_index].is_num()) { + int value = values[value_index]; + if (value < 0) { + return "unsigned integer is lower than minimum"; + } else if (value >= 0xd800 && value <= 0xdfff) { + return "unsigned integer is invalid Unicode character"; + } else if (value > 0x10ffff) { + return "unsigned integer is greater than maximum"; + } + str = chr(values[value_index]); + } else if (values[value_index].get_type() == Variant::STRING) { + str = values[value_index]; + if (str.length() != 1) { + return "%c requires number or single-character string"; + } + } else { + return "%c requires number or single-character string"; + } + + // Padding. + if (left_justified) { + str = str.rpad(min_chars); + } else { + str = str.lpad(min_chars); + } + + formatted += str; + ++value_index; + in_format = false; + break; + } + case '-': { // Left justify + left_justified = true; + break; + } + case '+': { // Show + if positive. + show_sign = true; + break; + } + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + int n = c - '0'; + if (in_decimals) { + min_decimals *= 10; + min_decimals += n; + } else { + if (c == '0' && min_chars == 0) { + if (left_justified) { + WARN_PRINT("'0' flag ignored with '-' flag in string format"); + } else { + pad_with_zeros = true; + } + } else { + min_chars *= 10; + min_chars += n; + } + } + break; + } + case '.': { // Float separator. + if (in_decimals) { + return "too many decimal points in format"; + } + in_decimals = true; + min_decimals = 0; // We want to add the value manually. + break; + } + + case '*': { // Dynamic width, based on value. + if (value_index >= values.size()) { + return "not enough arguments for format string"; + } + + if (!values[value_index].is_num()) { + return "* wants number"; + } + + int size = values[value_index]; + + if (in_decimals) { + min_decimals = size; + } else { + min_chars = size; + } + + ++value_index; + break; + } + + default: { + return "unsupported format character"; + } + } + } else { // Not in format string. + switch (c) { + case '%': + in_format = true; + // Back to defaults: + min_chars = 0; + min_decimals = 6; + pad_with_zeros = false; + left_justified = false; + show_sign = false; + in_decimals = false; + break; + default: + formatted += chr(c); + } + } + } + + if (in_format) { + return "incomplete format"; + } + + if (value_index != values.size()) { + return "not all arguments converted during string formatting"; + } + + if (error) { + *error = false; + } + return formatted; +} + +String String::quote(String quotechar) const { + return quotechar + *this + quotechar; +} + +String String::unquote() const { + if (!is_quoted()) { + return *this; + } + + return substr(1, length() - 2); +} + +String String::num(double p_num, int p_decimals) { + if (Math::is_nan(p_num)) { + return "nan"; + } + + if (Math::is_inf(p_num)) { + if (signbit(p_num)) { + return "-inf"; + } else { + return "inf"; + } + } + +#ifndef NO_USE_STDLIB + + if (p_decimals > 16) { + p_decimals = 16; + } + + char fmt[7]; + fmt[0] = '%'; + fmt[1] = '.'; + + if (p_decimals < 0) { + fmt[1] = 'l'; + fmt[2] = 'f'; + fmt[3] = 0; + + } else if (p_decimals < 10) { + fmt[2] = '0' + p_decimals; + fmt[3] = 'l'; + fmt[4] = 'f'; + fmt[5] = 0; + } else { + fmt[2] = '0' + (p_decimals / 10); + fmt[3] = '0' + (p_decimals % 10); + fmt[4] = 'l'; + fmt[5] = 'f'; + fmt[6] = 0; + } + char buf[256]; + +#if defined(__GNUC__) || defined(_MSC_VER) + snprintf(buf, 256, fmt, p_num); +#else + sprintf(buf, fmt, p_num); +#endif + + buf[255] = 0; + //destroy trailing zeroes + { + bool period = false; + int z = 0; + while (buf[z]) { + if (buf[z] == '.') { + period = true; + } + z++; + } + + if (period) { + z--; + while (z > 0) { + if (buf[z] == '0') { + buf[z] = 0; + } else if (buf[z] == '.') { + buf[z] = 0; + break; + } else { + break; + } + + z--; + } + } + } + + return buf; +#else + + String s; + String sd; + /* integer part */ + + bool neg = p_num < 0; + p_num = ABS(p_num); + int intn = (int)p_num; + + /* decimal part */ + + if (p_decimals > 0 || (p_decimals == -1 && (int)p_num != p_num)) { + double dec = p_num - (double)((int)p_num); + + int digit = 0; + if (p_decimals > MAX_DIGITS) + p_decimals = MAX_DIGITS; + + int dec_int = 0; + int dec_max = 0; + + while (true) { + dec *= 10.0; + dec_int = dec_int * 10 + (int)dec % 10; + dec_max = dec_max * 10 + 9; + digit++; + + if (p_decimals == -1) { + if (digit == MAX_DIGITS) //no point in going to infinite + break; + + if ((dec - (double)((int)dec)) < 1e-6) + break; + } + + if (digit == p_decimals) + break; + } + dec *= 10; + int last = (int)dec % 10; + + if (last > 5) { + if (dec_int == dec_max) { + dec_int = 0; + intn++; + } else { + dec_int++; + } + } + + String decimal; + for (int i = 0; i < digit; i++) { + char num[2] = { 0, 0 }; + num[0] = '0' + dec_int % 10; + decimal = num + decimal; + dec_int /= 10; + } + sd = '.' + decimal; + } + + if (intn == 0) + + s = "0"; + else { + while (intn) { + CharType num = '0' + (intn % 10); + intn /= 10; + s = num + s; + } + } + + s = s + sd; + if (neg) + s = "-" + s; + return s; +#endif +} + +String String::num_scientific(double p_num) { + if (Math::is_nan(p_num)) { + return "nan"; + } + + if (Math::is_inf(p_num)) { + if (signbit(p_num)) { + return "-inf"; + } else { + return "inf"; + } + } + +#ifndef NO_USE_STDLIB + + char buf[256]; + +#if defined(__GNUC__) || defined(_MSC_VER) + +#if defined(__MINGW32__) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT) + // MinGW requires _set_output_format() to conform to C99 output for printf + unsigned int old_exponent_format = _set_output_format(_TWO_DIGIT_EXPONENT); +#endif + snprintf(buf, 256, "%lg", p_num); + +#if defined(__MINGW32__) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT) + _set_output_format(old_exponent_format); +#endif + +#else + sprintf(buf, "%.16lg", p_num); +#endif + + buf[255] = 0; + + return buf; +#else + + return String::num(p_num); +#endif +} + +String String::num_real(double p_num) { + if (Math::is_nan(p_num)) { + return "nan"; + } + + if (Math::is_inf(p_num)) { + if (signbit(p_num)) { + return "-inf"; + } else { + return "inf"; + } + } + + String s; + String sd; + /* integer part */ + + bool neg = p_num < 0; + p_num = ABS(p_num); + int intn = (int)p_num; + + /* decimal part */ + + if ((int)p_num != p_num) { + double dec = p_num - (double)((int)p_num); + + int digit = 0; + int decimals = MAX_DIGITS; + + int dec_int = 0; + int dec_max = 0; + + while (true) { + dec *= 10.0; + dec_int = dec_int * 10 + (int)dec % 10; + dec_max = dec_max * 10 + 9; + digit++; + + if ((dec - (double)((int)dec)) < 1e-6) { + break; + } + + if (digit == decimals) { + break; + } + } + + dec *= 10; + int last = (int)dec % 10; + + if (last > 5) { + if (dec_int == dec_max) { + dec_int = 0; + intn++; + } else { + dec_int++; + } + } + + String decimal; + for (int i = 0; i < digit; i++) { + char num[2] = { 0, 0 }; + num[0] = '0' + dec_int % 10; + decimal = num + decimal; + dec_int /= 10; + } + sd = '.' + decimal; + } else { + sd = ".0"; + } + + if (intn == 0) { + s = "0"; + } else { + while (intn) { + CharType num = '0' + (intn % 10); + intn /= 10; + s = num + s; + } + } + + s = s + sd; + if (neg) { + s = "-" + s; + } + return s; +} + +String String::num_int64(int64_t p_num, int base, bool capitalize_hex) { + bool sign = p_num < 0; + + int64_t n = p_num; + + int chars = 0; + do { + n /= base; + chars++; + } while (n); + + if (sign) { + chars++; + } + String s; + s.resize(chars + 1); + CharType *c = s.ptrw(); + c[chars] = 0; + n = p_num; + do { + int mod = ABS(n % base); + if (mod >= 10) { + char a = (capitalize_hex ? 'A' : 'a'); + c[--chars] = a + (mod - 10); + } else { + c[--chars] = '0' + mod; + } + + n /= base; + } while (n); + + if (sign) { + c[0] = '-'; + } + + return s; +} + +String String::num_uint64(uint64_t p_num, int base, bool capitalize_hex) { + uint64_t n = p_num; + + int chars = 0; + do { + n /= base; + chars++; + } while (n); + + String s; + s.resize(chars + 1); + CharType *c = s.ptrw(); + c[chars] = 0; + n = p_num; + do { + int mod = n % base; + if (mod >= 10) { + char a = (capitalize_hex ? 'A' : 'a'); + c[--chars] = a + (mod - 10); + } else { + c[--chars] = '0' + mod; + } + + n /= base; + } while (n); + + return s; +} + +String String::chr(CharType p_char) { + CharType c[2] = { p_char, 0 }; + return String(c); +} + +String String::md5(const uint8_t *p_md5) { + return String::hex_encode_buffer(p_md5, 16); +} + +String String::hex_encode_buffer(const uint8_t *p_buffer, int p_len) { + static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + String ret; + char v[2] = { 0, 0 }; + + for (int i = 0; i < p_len; i++) { + v[0] = hex[p_buffer[i] >> 4]; + ret += v; + v[0] = hex[p_buffer[i] & 0xF]; + ret += v; + } + + return ret; +} + +String String::bool_num(bool p_val) { + if (p_val) { + return "1"; + } else { + return "0"; + } +} + +String String::bool_str(bool p_val) { + if (p_val) { + return "true"; + } else { + return "false"; + } +} + +bool String::is_numeric() const { + if (length() == 0) { + return false; + }; + + int s = 0; + if (operator[](0) == '-') { + ++s; + } + bool dot = false; + for (int i = s; i < length(); i++) { + CharType c = operator[](i); + if (c == '.') { + if (dot) { + return false; + }; + dot = true; + } + if (c < '0' || c > '9') { + return false; + }; + }; + + return true; // TODO: Use the parser below for this instead +}; + +bool String::is_zero() const { + int size = length(); + + if (size == 0) { + return false; + } + + int starti = 0; + + if (operator[](0) == '-') { + starti += 1; + } + + bool had_dot = false; + for (int i = starti; i < size; ++i) { + CharType c = operator[](i); + + if (c == '.') { + if (!had_dot) { + had_dot = true; + continue; + } else { + return false; + } + } + + if (c != '0') { + return false; + } + } + + return true; } String String::capitalize() const { @@ -663,7 +2445,7 @@ String String::capitalize() const { } String String::camelcase_to_underscore(bool lowercase) const { - const CharType *cstr = c_str(); + const CharType *cstr = get_data(); String new_string; const char A = 'A', Z = 'Z'; const char a = 'a', z = 'z'; @@ -704,6 +2486,18 @@ String String::camelcase_to_underscore(bool lowercase) const { return lowercase ? new_string.to_lower() : new_string; } +String String::get_with_code_lines() const { + const Vector lines = split("\n"); + String ret; + for (int i = 0; i < lines.size(); i++) { + if (i > 0) { + ret += "\n"; + } + ret += vformat("%4d | %s", i + 1, lines[i]); + } + return ret; +} + int String::get_slice_count(String p_splitter) const { if (empty()) { return 0; @@ -792,43 +2586,6 @@ String String::get_slicec(CharType p_splitter, int p_slice) const { } } -Vector String::split_spaces() const { - Vector ret; - int from = 0; - int i = 0; - int len = length(); - if (len == 0) { - return ret; - } - - bool inside = false; - - while (true) { - bool empty = operator[](i) < 33; - - if (i == 0) { - inside = !empty; - } - - if (!empty && !inside) { - inside = true; - from = i; - } - - if (empty && inside) { - ret.push_back(substr(from, i - from)); - inside = false; - } - - if (i == len) { - break; - } - i++; - } - - return ret; -} - Vector String::split(const String &p_splitter, bool p_allow_empty, int p_maxsplit) const { Vector ret; int from = 0; @@ -898,6 +2655,43 @@ Vector String::rsplit(const String &p_splitter, bool p_allow_empty, int return ret; } +Vector String::split_spaces() const { + Vector ret; + int from = 0; + int i = 0; + int len = length(); + if (len == 0) { + return ret; + } + + bool inside = false; + + while (true) { + bool empty = operator[](i) < 33; + + if (i == 0) { + inside = !empty; + } + + if (!empty && !inside) { + inside = true; + from = i; + } + + if (empty && inside) { + ret.push_back(substr(from, i - from)); + inside = false; + } + + if (i == len) { + break; + } + i++; + } + + return ret; +} + Vector String::split_floats(const String &p_splitter, bool p_allow_empty) const { Vector ret; int from = 0; @@ -909,7 +2703,7 @@ Vector String::split_floats(const String &p_splitter, bool p_allow_empty) end = len; } if (p_allow_empty || (end > from)) { - ret.push_back(String::to_double(&c_str()[from])); + ret.push_back(String::to_double(&get_data()[from])); } if (end == len) { @@ -938,7 +2732,7 @@ Vector String::split_floats_mk(const Vector &p_splitters, bool p_ } if (p_allow_empty || (end > from)) { - ret.push_back(String::to_double(&c_str()[from])); + ret.push_back(String::to_double(&get_data()[from])); } if (end == len) { @@ -962,7 +2756,7 @@ Vector String::split_ints(const String &p_splitter, bool p_allow_empty) con end = len; } if (p_allow_empty || (end > from)) { - ret.push_back(String::to_int(&c_str()[from], end - from)); + ret.push_back(String::to_int(&get_data()[from], end - from)); } if (end == len) { @@ -991,7 +2785,7 @@ Vector String::split_ints_mk(const Vector &p_splitters, bool p_allo } if (p_allow_empty || (end > from)) { - ret.push_back(String::to_int(&c_str()[from], end - from)); + ret.push_back(String::to_int(&get_data()[from], end - from)); } if (end == len) { @@ -1023,20 +2817,6 @@ CharType String::char_lowercase(CharType p_char) { return _find_lower(p_char); } -String String::to_upper() const { - String upper = *this; - - for (int i = 0; i < upper.size(); i++) { - const CharType s = upper[i]; - const CharType t = _find_upper(s); - if (s != t) { // avoid copy on write - upper[i] = t; - } - } - - return upper; -} - String String::to_lower() const { String lower = *this; @@ -1051,378 +2831,272 @@ String String::to_lower() const { return lower; } -const CharType *String::c_str() const { - static const CharType zero = 0; +String String::to_upper() const { + String upper = *this; - return size() ? &operator[](0) : &zero; -} - -String String::md5(const uint8_t *p_md5) { - return String::hex_encode_buffer(p_md5, 16); -} - -String String::hex_encode_buffer(const uint8_t *p_buffer, int p_len) { - static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - - String ret; - char v[2] = { 0, 0 }; - - for (int i = 0; i < p_len; i++) { - v[0] = hex[p_buffer[i] >> 4]; - ret += v; - v[0] = hex[p_buffer[i] & 0xF]; - ret += v; - } - - return ret; -} - -String String::bool_num(bool p_val) { - if (p_val) { - return "1"; - } else { - return "0"; - } -} - -String String::bool_str(bool p_val) { - if (p_val) { - return "true"; - } else { - return "false"; - } -} - -String String::chr(CharType p_char) { - CharType c[2] = { p_char, 0 }; - return String(c); -} -String String::num(double p_num, int p_decimals) { - if (Math::is_nan(p_num)) { - return "nan"; - } - -#ifndef NO_USE_STDLIB - - if (p_decimals > 16) { - p_decimals = 16; - } - - char fmt[7]; - fmt[0] = '%'; - fmt[1] = '.'; - - if (p_decimals < 0) { - fmt[1] = 'l'; - fmt[2] = 'f'; - fmt[3] = 0; - - } else if (p_decimals < 10) { - fmt[2] = '0' + p_decimals; - fmt[3] = 'l'; - fmt[4] = 'f'; - fmt[5] = 0; - } else { - fmt[2] = '0' + (p_decimals / 10); - fmt[3] = '0' + (p_decimals % 10); - fmt[4] = 'l'; - fmt[5] = 'f'; - fmt[6] = 0; - } - char buf[256]; - -#if defined(__GNUC__) || defined(_MSC_VER) - snprintf(buf, 256, fmt, p_num); -#else - sprintf(buf, fmt, p_num); -#endif - - buf[255] = 0; - //destroy trailing zeroes - { - bool period = false; - int z = 0; - while (buf[z]) { - if (buf[z] == '.') { - period = true; - } - z++; + for (int i = 0; i < upper.size(); i++) { + const CharType s = upper[i]; + const CharType t = _find_upper(s); + if (s != t) { // avoid copy on write + upper[i] = t; } + } - if (period) { - z--; - while (z > 0) { - if (buf[z] == '0') { - buf[z] = 0; - } else if (buf[z] == '.') { - buf[z] = 0; - break; - } else { - break; + return upper; +} + +int String::_count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const { + if (p_string.empty()) { + return 0; + } + int len = length(); + int slen = p_string.length(); + if (len < slen) { + return 0; + } + String str; + if (p_from >= 0 && p_to >= 0) { + if (p_to == 0) { + p_to = len; + } else if (p_from >= p_to) { + return 0; + } + if (p_from == 0 && p_to == len) { + str = String(); + str.copy_from_unchecked(&get_data()[0], len); + } else { + str = substr(p_from, p_to - p_from); + } + } else { + return 0; + } + int c = 0; + int idx = -1; + do { + idx = p_case_insensitive ? str.findn(p_string) : str.find(p_string); + if (idx != -1) { + str = str.substr(idx + slen, str.length() - slen); + ++c; + } + } while (idx != -1); + return c; +} + +int String::count(const String &p_string, int p_from, int p_to) const { + return _count(p_string, p_from, p_to, false); +} + +int String::countn(const String &p_string, int p_from, int p_to) const { + return _count(p_string, p_from, p_to, true); +} + +String String::left(int p_pos) const { + if (p_pos <= 0) { + return ""; + } + + if (p_pos >= length()) { + return *this; + } + + return substr(0, p_pos); +} + +String String::right(int p_pos) const { + if (p_pos >= length()) { + return ""; + } + + if (p_pos <= 0) { + return *this; + } + + return substr(p_pos, (length() - p_pos)); +} + +String String::indent(const String &p_prefix) const { + String new_string; + int line_start = 0; + + for (int i = 0; i < length(); i++) { + const CharType c = operator[](i); + if (c == '\n') { + if (i == line_start) { + new_string += c; // Leave empty lines empty. + } else { + new_string += p_prefix + substr(line_start, i - line_start + 1); + } + line_start = i + 1; + } + } + if (line_start != length()) { + new_string += p_prefix + substr(line_start); + } + return new_string; +} + +String String::dedent() const { + String new_string; + String indent; + bool has_indent = false; + bool has_text = false; + int line_start = 0; + int indent_stop = -1; + + for (int i = 0; i < length(); i++) { + CharType c = operator[](i); + if (c == '\n') { + if (has_text) { + new_string += substr(indent_stop, i - indent_stop); + } + new_string += "\n"; + has_text = false; + line_start = i + 1; + indent_stop = -1; + } else if (!has_text) { + if (c > 32) { + has_text = true; + if (!has_indent) { + has_indent = true; + indent = substr(line_start, i - line_start); + indent_stop = i; + } + } + if (has_indent && indent_stop < 0) { + int j = i - line_start; + if (j >= indent.length() || c != indent[j]) { + indent_stop = i; } - - z--; } } } - return buf; -#else + if (has_text) { + new_string += substr(indent_stop, length() - indent_stop); + } - String s; - String sd; - /* integer part */ + return new_string; +} - bool neg = p_num < 0; - p_num = ABS(p_num); - int intn = (int)p_num; +String String::strip_edges(bool left, bool right) const { + int len = length(); + int beg = 0, end = len; - /* decimal part */ - - if (p_decimals > 0 || (p_decimals == -1 && (int)p_num != p_num)) { - double dec = p_num - (float)((int)p_num); - - int digit = 0; - if (p_decimals > MAX_DIGITS) - p_decimals = MAX_DIGITS; - - int dec_int = 0; - int dec_max = 0; - - while (true) { - dec *= 10.0; - dec_int = dec_int * 10 + (int)dec % 10; - dec_max = dec_max * 10 + 9; - digit++; - - if (p_decimals == -1) { - if (digit == MAX_DIGITS) //no point in going to infinite - break; - - if ((dec - (float)((int)dec)) < 1e-6) - break; - } - - if (digit == p_decimals) - break; - } - dec *= 10; - int last = (int)dec % 10; - - if (last > 5) { - if (dec_int == dec_max) { - dec_int = 0; - intn++; + if (left) { + for (int i = 0; i < len; i++) { + if (operator[](i) <= 32) { + beg++; } else { - dec_int++; - } - } - - String decimal; - for (int i = 0; i < digit; i++) { - char num[2] = { 0, 0 }; - num[0] = '0' + dec_int % 10; - decimal = num + decimal; - dec_int /= 10; - } - sd = '.' + decimal; - } - - if (intn == 0) - - s = "0"; - else { - while (intn) { - CharType num = '0' + (intn % 10); - intn /= 10; - s = num + s; - } - } - - s = s + sd; - if (neg) - s = "-" + s; - return s; -#endif -} - -String String::num_int64(int64_t p_num, int base, bool capitalize_hex) { - bool sign = p_num < 0; - - int64_t n = p_num; - - int chars = 0; - do { - n /= base; - chars++; - } while (n); - - if (sign) { - chars++; - } - String s; - s.resize(chars + 1); - CharType *c = s.ptrw(); - c[chars] = 0; - n = p_num; - do { - int mod = ABS(n % base); - if (mod >= 10) { - char a = (capitalize_hex ? 'A' : 'a'); - c[--chars] = a + (mod - 10); - } else { - c[--chars] = '0' + mod; - } - - n /= base; - } while (n); - - if (sign) { - c[0] = '-'; - } - - return s; -} - -String String::num_uint64(uint64_t p_num, int base, bool capitalize_hex) { - uint64_t n = p_num; - - int chars = 0; - do { - n /= base; - chars++; - } while (n); - - String s; - s.resize(chars + 1); - CharType *c = s.ptrw(); - c[chars] = 0; - n = p_num; - do { - int mod = n % base; - if (mod >= 10) { - char a = (capitalize_hex ? 'A' : 'a'); - c[--chars] = a + (mod - 10); - } else { - c[--chars] = '0' + mod; - } - - n /= base; - } while (n); - - return s; -} - -String String::num_real(double p_num) { - String s; - String sd; - /* integer part */ - - bool neg = p_num < 0; - p_num = ABS(p_num); - int intn = (int)p_num; - - /* decimal part */ - - if ((int)p_num != p_num) { - double dec = p_num - (float)((int)p_num); - - int digit = 0; - int decimals = MAX_DIGITS; - - int dec_int = 0; - int dec_max = 0; - - while (true) { - dec *= 10.0; - dec_int = dec_int * 10 + (int)dec % 10; - dec_max = dec_max * 10 + 9; - digit++; - - if ((dec - (float)((int)dec)) < 1e-6) { - break; - } - - if (digit == decimals) { break; } } + } - dec *= 10; - int last = (int)dec % 10; - - if (last > 5) { - if (dec_int == dec_max) { - dec_int = 0; - intn++; + if (right) { + for (int i = (int)(len - 1); i >= 0; i--) { + if (operator[](i) <= 32) { + end--; } else { - dec_int++; + break; } } - - String decimal; - for (int i = 0; i < digit; i++) { - char num[2] = { 0, 0 }; - num[0] = '0' + dec_int % 10; - decimal = num + decimal; - dec_int /= 10; - } - sd = '.' + decimal; - } else { - sd = ".0"; } - if (intn == 0) { - s = "0"; - } else { - while (intn) { - CharType num = '0' + (intn % 10); - intn /= 10; - s = num + s; + if (beg == 0 && end == len) { + return *this; + } + + return substr(beg, end - beg); +} + +String String::strip_escapes() const { + String new_string; + for (int i = 0; i < length(); i++) { + // Escape characters on first page of the ASCII table, before 32 (Space). + if (operator[](i) < 32) { + continue; + } + new_string += operator[](i); + } + + return new_string; +} + +String String::lstrip(const String &p_chars) const { + int len = length(); + int beg; + + for (beg = 0; beg < len; beg++) { + if (p_chars.find_char(get(beg)) == -1) { + break; } } - s = s + sd; - if (neg) { - s = "-" + s; - } - return s; -} - -String String::num_scientific(double p_num) { - if (Math::is_nan(p_num)) { - return "nan"; + if (beg == 0) { + return *this; } -#ifndef NO_USE_STDLIB - - char buf[256]; - -#if defined(__GNUC__) || defined(_MSC_VER) - -#if defined(__MINGW32__) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT) - // MinGW requires _set_output_format() to conform to C99 output for printf - unsigned int old_exponent_format = _set_output_format(_TWO_DIGIT_EXPONENT); -#endif - snprintf(buf, 256, "%lg", p_num); - -#if defined(__MINGW32__) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT) - _set_output_format(old_exponent_format); -#endif - -#else - sprintf(buf, "%.16lg", p_num); -#endif - - buf[255] = 0; - - return buf; -#else - - return String::num(p_num); -#endif + return substr(beg, len - beg); } +String String::rstrip(const String &p_chars) const { + int len = length(); + int end; + + for (end = len - 1; end >= 0; end--) { + if (p_chars.find_char(get(end)) == -1) { + break; + } + } + + if (end == len - 1) { + return *this; + } + + return substr(0, end + 1); +} + +String String::get_extension() const { + int pos = rfind("."); + if (pos < 0 || pos < MAX(rfind("/"), rfind("\\"))) { + return ""; + } + + return substr(pos + 1, length()); +} + +String String::get_basename() const { + int pos = rfind("."); + if (pos < 0 || pos < MAX(rfind("/"), rfind("\\"))) { + return *this; + } + + return substr(0, pos); +} + +String String::plus_file(const String &p_file) const { + if (empty()) { + return p_file; + } + if (operator[](length() - 1) == '/' || (p_file.size() > 0 && p_file.operator[](0) == '/')) { + return *this + p_file; + } + return *this + "/" + p_file; +} + +CharType String::unicode_at(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, length(), 0); + return operator[](p_idx); +} + +CharType String::ord_at(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, length(), 0); + return operator[](p_idx); +} + +void String::erase(int p_pos, int p_chars) { + *this = left(p_pos) + substr(p_pos + p_chars, length() - ((p_pos + p_chars))); +} CharString String::ascii(bool p_allow_extended) const { if (!length()) { return CharString(); @@ -1432,7 +3106,13 @@ CharString String::ascii(bool p_allow_extended) const { cs.resize(size()); for (int i = 0; i < size(); i++) { - cs[i] = operator[](i); + CharType c = operator[](i); + if ((c <= 0x7f) || (c <= 0xff && p_allow_extended)) { + cs[i] = c; + } else { + print_unicode_error(vformat("Invalid unicode codepoint (%x), cannot represent as ASCII/Latin-1", (uint32_t)c)); + cs[i] = 0x20; + } } return cs; @@ -1443,13 +3123,11 @@ String String::utf8(const char *p_utf8, int p_len) { ret.parse_utf8(p_utf8, p_len); return ret; -}; - -bool String::parse_utf8(const char *p_utf8, int p_len, bool p_skip_cr) { -#define _UNICERROR(m_err) print_line("Unicode error: " + String(m_err)); +} +Error String::parse_utf8(const char *p_utf8, int p_len, bool p_skip_cr) { if (!p_utf8) { - return true; + return ERR_INVALID_DATA; } String aux; @@ -1459,9 +3137,9 @@ bool String::parse_utf8(const char *p_utf8, int p_len, bool p_skip_cr) { /* HANDLE BOM (Byte Order Mark) */ if (p_len < 0 || p_len >= 3) { - bool has_bom = uint8_t(p_utf8[0]) == 0xEF && uint8_t(p_utf8[1]) == 0xBB && uint8_t(p_utf8[2]) == 0xBF; + bool has_bom = uint8_t(p_utf8[0]) == 0xef && uint8_t(p_utf8[1]) == 0xbb && uint8_t(p_utf8[2]) == 0xbf; if (has_bom) { - //just skip it + //8-bit encoding, byte order has no meaning in UTF-8, just skip it if (p_len >= 0) { p_len -= 3; } @@ -1469,52 +3147,58 @@ bool String::parse_utf8(const char *p_utf8, int p_len, bool p_skip_cr) { } } + bool decode_error = false; + bool decode_failed = false; { const char *ptrtmp = p_utf8; const char *ptrtmp_limit = &p_utf8[p_len]; int skip = 0; + uint8_t c_start = 0; while (ptrtmp != ptrtmp_limit && *ptrtmp) { - if (skip == 0) { - uint8_t c = *ptrtmp >= 0 ? *ptrtmp : uint8_t(256 + *ptrtmp); + uint8_t c = *ptrtmp >= 0 ? *ptrtmp : uint8_t(256 + *ptrtmp); + if (skip == 0) { if (p_skip_cr && c == '\r') { ptrtmp++; continue; } - /* Determine the number of characters in sequence */ if ((c & 0x80) == 0) { skip = 0; - } else if ((c & 0xE0) == 0xC0) { + } else if ((c & 0xe0) == 0xc0) { skip = 1; - } else if ((c & 0xF0) == 0xE0) { + } else if ((c & 0xf0) == 0xe0) { skip = 2; - } else if ((c & 0xF8) == 0xF0) { + } else if ((c & 0xf8) == 0xf0) { skip = 3; - if (sizeof(wchar_t) == 2) { - str_size++; // encode as surrogate pair. - } - } else if ((c & 0xFC) == 0xF8) { + } else if ((c & 0xfc) == 0xf8) { skip = 4; - // invalid character, too long to encode as surrogates. - } else if ((c & 0xFE) == 0xFC) { + } else if ((c & 0xfe) == 0xfc) { skip = 5; - // invalid character, too long to encode as surrogates. } else { - _UNICERROR("invalid skip"); - return true; //invalid utf8 + skip = 0; + print_unicode_error(vformat("Invalid UTF-8 leading byte (%x)", c), true); + decode_failed = true; } + c_start = c; - if (skip == 1 && (c & 0x1E) == 0) { - //printf("overlong rejected\n"); - _UNICERROR("overlong rejected"); - return true; //reject overlong + if (skip == 1 && (c & 0x1e) == 0) { + print_unicode_error(vformat("Overlong encoding (%x ...)", c)); + decode_error = true; } - str_size++; - } else { - --skip; + if ((c_start == 0xe0 && skip == 2 && c < 0xa0) || (c_start == 0xf0 && skip == 3 && c < 0x90) || (c_start == 0xf8 && skip == 4 && c < 0x88) || (c_start == 0xfc && skip == 5 && c < 0x84)) { + print_unicode_error(vformat("Overlong encoding (%x %x ...)", c_start, c)); + decode_error = true; + } + if (c < 0x80 || c > 0xbf) { + print_unicode_error(vformat("Invalid UTF-8 continuation byte (%x ... %x ...)", c_start, c), true); + decode_failed = true; + skip = 0; + } else { + --skip; + } } cstr_size++; @@ -1522,93 +3206,95 @@ bool String::parse_utf8(const char *p_utf8, int p_len, bool p_skip_cr) { } if (skip) { - _UNICERROR("no space left"); - return true; //not enough spac + print_unicode_error(vformat("Missing %d UTF-8 continuation byte(s)", skip), true); + decode_failed = true; } } if (str_size == 0) { clear(); - return false; + return OK; // empty string } resize(str_size + 1); CharType *dst = ptrw(); dst[str_size] = 0; + int skip = 0; + uint32_t unichar = 0; while (cstr_size) { - int len = 0; + uint8_t c = *p_utf8 >= 0 ? *p_utf8 : uint8_t(256 + *p_utf8); - if (p_skip_cr && *p_utf8 == '\r') { - p_utf8++; - continue; - } - - /* Determine the number of characters in sequence */ - if ((*p_utf8 & 0x80) == 0) { - len = 1; - } else if ((*p_utf8 & 0xE0) == 0xC0) { - len = 2; - } else if ((*p_utf8 & 0xF0) == 0xE0) { - len = 3; - } else if ((*p_utf8 & 0xF8) == 0xF0) { - len = 4; - } else if ((*p_utf8 & 0xFC) == 0xF8) { - len = 5; - } else if ((*p_utf8 & 0xFE) == 0xFC) { - len = 6; + if (skip == 0) { + if (p_skip_cr && c == '\r') { + p_utf8++; + continue; + } + /* Determine the number of characters in sequence */ + if ((c & 0x80) == 0) { + *(dst++) = c; + unichar = 0; + skip = 0; + } else if ((c & 0xe0) == 0xc0) { + unichar = (0xff >> 3) & c; + skip = 1; + } else if ((c & 0xf0) == 0xe0) { + unichar = (0xff >> 4) & c; + skip = 2; + } else if ((c & 0xf8) == 0xf0) { + unichar = (0xff >> 5) & c; + skip = 3; + } else if ((c & 0xfc) == 0xf8) { + unichar = (0xff >> 6) & c; + skip = 4; + } else if ((c & 0xfe) == 0xfc) { + unichar = (0xff >> 7) & c; + skip = 5; + } else { + *(dst++) = 0x20; + unichar = 0; + skip = 0; + } } else { - _UNICERROR("invalid len"); - - return true; //invalid UTF8 - } - - if (len > cstr_size) { - _UNICERROR("no space left"); - return true; //not enough space - } - - if (len == 2 && (*p_utf8 & 0x1E) == 0) { - //printf("overlong rejected\n"); - _UNICERROR("no space left"); - return true; //reject overlong - } - - /* Convert the first character */ - - uint32_t unichar = 0; - - if (len == 1) { - unichar = *p_utf8; - } else { - unichar = (0xFF >> (len + 1)) & *p_utf8; - - for (int i = 1; i < len; i++) { - if ((p_utf8[i] & 0xC0) != 0x80) { - _UNICERROR("invalid utf8"); - return true; //invalid utf8 + if (c < 0x80 || c > 0xbf) { + *(dst++) = 0x20; + skip = 0; + } else { + unichar = (unichar << 6) | (c & 0x3f); + --skip; + if (skip == 0) { + if (unichar == 0) { + print_unicode_error("NUL character", true); + decode_failed = true; + unichar = 0x20; + } + if ((unichar & 0xfffff800) == 0xd800) { + print_unicode_error(vformat("Unpaired surrogate (%x)", unichar)); + decode_error = true; + } + if (unichar > 0x10ffff) { + print_unicode_error(vformat("Invalid unicode codepoint (%x)", unichar)); + decode_error = true; + } + *(dst++) = unichar; } - if (unichar == 0 && i == 2 && ((p_utf8[i] & 0x7F) >> (7 - len)) == 0) { - _UNICERROR("invalid utf8 overlong"); - return true; //no overlong - } - unichar = (unichar << 6) | (p_utf8[i] & 0x3F); } } - if (sizeof(wchar_t) == 2 && unichar > 0x10FFFF) { - unichar = ' '; // invalid character, too long to encode as surrogates. - } else if (sizeof(wchar_t) == 2 && unichar > 0xFFFF) { - *(dst++) = uint32_t((unichar >> 10) + 0xD7C0); // lead surrogate. - *(dst++) = uint32_t((unichar & 0x3FF) | 0xDC00); // trail surrogate. - } else { - *(dst++) = unichar; - } - cstr_size -= len; - p_utf8 += len; + cstr_size--; + p_utf8++; + } + if (skip) { + *(dst++) = 0x20; } - return false; + if (decode_failed) { + return ERR_INVALID_DATA; + } else if (decode_error) { + return ERR_PARSE_ERROR; + } else { + return OK; + } } CharString String::utf8() const { @@ -1621,18 +3307,6 @@ CharString String::utf8() const { int fl = 0; for (int i = 0; i < l; i++) { uint32_t c = d[i]; - if ((c & 0xfffffc00) == 0xd800) { // decode surrogate pair. - if ((i < l - 1) && (d[i + 1] & 0xfffffc00) == 0xdc00) { - c = (c << 10UL) + d[i + 1] - ((0xd800 << 10UL) + 0xdc00 - 0x10000); - i++; // skip trail surrogate. - } else { - fl += 1; - continue; - } - } else if ((c & 0xfffffc00) == 0xdc00) { - fl += 1; - continue; - } if (c <= 0x7f) { // 7 bits. fl += 1; } else if (c <= 0x7ff) { // 11 bits @@ -1643,8 +3317,13 @@ CharString String::utf8() const { fl += 4; } else if (c <= 0x03ffffff) { // 26 bits fl += 5; + print_unicode_error(vformat("Invalid unicode codepoint (%x)", c)); } else if (c <= 0x7fffffff) { // 31 bits fl += 6; + print_unicode_error(vformat("Invalid unicode codepoint (%x)", c)); + } else { + fl += 1; + print_unicode_error(vformat("Invalid unicode codepoint (%x), cannot represent as UTF-8", c), true); } } @@ -1660,51 +3339,36 @@ CharString String::utf8() const { for (int i = 0; i < l; i++) { uint32_t c = d[i]; - if ((c & 0xfffffc00) == 0xd800) { // decode surrogate pair. - if ((i < l - 1) && (d[i + 1] & 0xfffffc00) == 0xdc00) { - c = (c << 10UL) + d[i + 1] - ((0xd800 << 10UL) + 0xdc00 - 0x10000); - i++; // skip trail surrogate. - } else { - APPEND_CHAR(' '); - continue; - } - } else if ((c & 0xfffffc00) == 0xdc00) { - APPEND_CHAR(' '); - continue; - } if (c <= 0x7f) { // 7 bits. APPEND_CHAR(c); } else if (c <= 0x7ff) { // 11 bits - APPEND_CHAR(uint32_t(0xc0 | ((c >> 6) & 0x1f))); // Top 5 bits. APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits. } else if (c <= 0xffff) { // 16 bits - APPEND_CHAR(uint32_t(0xe0 | ((c >> 12) & 0x0f))); // Top 4 bits. APPEND_CHAR(uint32_t(0x80 | ((c >> 6) & 0x3f))); // Middle 6 bits. APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits. } else if (c <= 0x001fffff) { // 21 bits - APPEND_CHAR(uint32_t(0xf0 | ((c >> 18) & 0x07))); // Top 3 bits. APPEND_CHAR(uint32_t(0x80 | ((c >> 12) & 0x3f))); // Upper middle 6 bits. APPEND_CHAR(uint32_t(0x80 | ((c >> 6) & 0x3f))); // Lower middle 6 bits. APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits. } else if (c <= 0x03ffffff) { // 26 bits - APPEND_CHAR(uint32_t(0xf8 | ((c >> 24) & 0x03))); // Top 2 bits. APPEND_CHAR(uint32_t(0x80 | ((c >> 18) & 0x3f))); // Upper middle 6 bits. APPEND_CHAR(uint32_t(0x80 | ((c >> 12) & 0x3f))); // middle 6 bits. APPEND_CHAR(uint32_t(0x80 | ((c >> 6) & 0x3f))); // Lower middle 6 bits. APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits. } else if (c <= 0x7fffffff) { // 31 bits - APPEND_CHAR(uint32_t(0xfc | ((c >> 30) & 0x01))); // Top 1 bit. APPEND_CHAR(uint32_t(0x80 | ((c >> 24) & 0x3f))); // Upper upper middle 6 bits. APPEND_CHAR(uint32_t(0x80 | ((c >> 18) & 0x3f))); // Lower upper middle 6 bits. APPEND_CHAR(uint32_t(0x80 | ((c >> 12) & 0x3f))); // Upper lower middle 6 bits. APPEND_CHAR(uint32_t(0x80 | ((c >> 6) & 0x3f))); // Lower lower middle 6 bits. APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits. + } else { + APPEND_CHAR(0x20); } } #undef APPEND_CHAR @@ -1713,10 +3377,1531 @@ CharString String::utf8() const { return utf8s; } +String String::utf16(const char16_t *p_utf16, int p_len) { + String ret; + ret.parse_utf16(p_utf16, p_len); + + return ret; +} + +Error String::parse_utf16(const char16_t *p_utf16, int p_len) { + if (!p_utf16) { + return ERR_INVALID_DATA; + } + + String aux; + + int cstr_size = 0; + int str_size = 0; + + /* HANDLE BOM (Byte Order Mark) */ + bool byteswap = false; // assume correct endianness if no BOM found + if (p_len < 0 || p_len >= 1) { + bool has_bom = false; + if (uint16_t(p_utf16[0]) == 0xfeff) { // correct BOM, read as is + has_bom = true; + byteswap = false; + } else if (uint16_t(p_utf16[0]) == 0xfffe) { // backwards BOM, swap bytes + has_bom = true; + byteswap = true; + } + if (has_bom) { + if (p_len >= 0) { + p_len -= 1; + } + p_utf16 += 1; + } + } + + bool decode_error = false; + { + const char16_t *ptrtmp = p_utf16; + const char16_t *ptrtmp_limit = &p_utf16[p_len]; + uint32_t c_prev = 0; + bool skip = false; + while (ptrtmp != ptrtmp_limit && *ptrtmp) { + uint32_t c = (byteswap) ? BSWAP16(*ptrtmp) : *ptrtmp; + + if ((c & 0xfffffc00) == 0xd800) { // lead surrogate + if (skip) { + print_unicode_error(vformat("Unpaired lead surrogate (%x [trail?] %x)", c_prev, c)); + decode_error = true; + } + skip = true; + } else if ((c & 0xfffffc00) == 0xdc00) { // trail surrogate + if (skip) { + str_size--; + } else { + print_unicode_error(vformat("Unpaired trail surrogate (%x [lead?] %x)", c_prev, c)); + decode_error = true; + } + skip = false; + } else { + skip = false; + } + + c_prev = c; + str_size++; + cstr_size++; + ptrtmp++; + } + + if (skip) { + print_unicode_error(vformat("Unpaired lead surrogate (%x [eol])", c_prev)); + decode_error = true; + } + } + + if (str_size == 0) { + clear(); + return OK; // empty string + } + + resize(str_size + 1); + CharType *dst = ptrw(); + dst[str_size] = 0; + + bool skip = false; + uint32_t c_prev = 0; + while (cstr_size) { + uint32_t c = (byteswap) ? BSWAP16(*p_utf16) : *p_utf16; + + if ((c & 0xfffffc00) == 0xd800) { // lead surrogate + if (skip) { + *(dst++) = c_prev; // unpaired, store as is + } + skip = true; + } else if ((c & 0xfffffc00) == 0xdc00) { // trail surrogate + if (skip) { + *(dst++) = (c_prev << 10UL) + c - ((0xd800 << 10UL) + 0xdc00 - 0x10000); // decode pair + } else { + *(dst++) = c; // unpaired, store as is + } + skip = false; + } else { + *(dst++) = c; + skip = false; + } + + cstr_size--; + p_utf16++; + c_prev = c; + } + + if (skip) { + *(dst++) = c_prev; + } + + if (decode_error) { + return ERR_PARSE_ERROR; + } else { + return OK; + } +} + +Char16String String::utf16() const { + int l = length(); + if (!l) { + return Char16String(); + } + + const CharType *d = &operator[](0); + int fl = 0; + for (int i = 0; i < l; i++) { + uint32_t c = d[i]; + if (c <= 0xffff) { // 16 bits. + fl += 1; + if ((c & 0xfffff800) == 0xd800) { + print_unicode_error(vformat("Unpaired surrogate (%x)", c)); + } + } else if (c <= 0x10ffff) { // 32 bits. + fl += 2; + } else { + print_unicode_error(vformat("Invalid unicode codepoint (%x), cannot represent as UTF-16", c), true); + fl += 1; + } + } + + Char16String utf16s; + if (fl == 0) { + return utf16s; + } + + utf16s.resize(fl + 1); + uint16_t *cdst = (uint16_t *)utf16s.get_data(); + +#define APPEND_CHAR(m_c) *(cdst++) = m_c + + for (int i = 0; i < l; i++) { + uint32_t c = d[i]; + + if (c <= 0xffff) { // 16 bits. + APPEND_CHAR(c); + } else if (c <= 0x10ffff) { // 32 bits. + APPEND_CHAR(uint32_t((c >> 10) + 0xd7c0)); // lead surrogate. + APPEND_CHAR(uint32_t((c & 0x3ff) | 0xdc00)); // trail surrogate. + } else { + APPEND_CHAR(0x20); + } + } +#undef APPEND_CHAR + *cdst = 0; //trailing zero + + return utf16s; +} + +uint32_t String::hash(const char *p_cstr) { + uint32_t hashv = 5381; + uint32_t c; + + while ((c = *p_cstr++)) { + hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ + } + + return hashv; +} + +uint32_t String::hash(const char *p_cstr, int p_len) { + uint32_t hashv = 5381; + for (int i = 0; i < p_len; i++) { + hashv = ((hashv << 5) + hashv) + p_cstr[i]; /* hash * 33 + c */ + } + + return hashv; +} + +uint32_t String::hash(const wchar_t *p_cstr) { + uint32_t hashv = 5381; + uint32_t c; + + while ((c = *p_cstr++)) { + hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ + } + + return hashv; +} + +uint32_t String::hash(const wchar_t *p_cstr, int p_len) { + uint32_t hashv = 5381; + for (int i = 0; i < p_len; i++) { + hashv = ((hashv << 5) + hashv) + p_cstr[i]; /* hash * 33 + c */ + } + + return hashv; +} + +uint32_t String::hash(const CharType *p_cstr) { + uint32_t hashv = 5381; + uint32_t c; + + while ((c = *p_cstr++)) { + hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ + } + + return hashv; +} + +uint32_t String::hash(const CharType *p_cstr, int p_len) { + uint32_t hashv = 5381; + for (int i = 0; i < p_len; i++) { + hashv = ((hashv << 5) + hashv) + p_cstr[i]; /* hash * 33 + c */ + } + + return hashv; +} + +uint32_t String::hash() const { + /* simple djb2 hashing */ + + const CharType *chr = get_data(); + uint32_t hashv = 5381; + uint32_t c; + + while ((c = *chr++)) { + hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ + } + + return hashv; +} + +uint64_t String::hash64() const { + /* simple djb2 hashing */ + + const CharType *chr = get_data(); + uint64_t hashv = 5381; + uint64_t c; + + while ((c = *chr++)) { + hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ + } + + return hashv; +} + +String String::md5_text() const { + CharString cs = utf8(); + unsigned char hash[16]; + CryptoCore::md5((unsigned char *)cs.ptr(), cs.length(), hash); + return String::hex_encode_buffer(hash, 16); +} + +String String::sha1_text() const { + CharString cs = utf8(); + unsigned char hash[20]; + CryptoCore::sha1((unsigned char *)cs.ptr(), cs.length(), hash); + return String::hex_encode_buffer(hash, 20); +} + +String String::sha256_text() const { + CharString cs = utf8(); + unsigned char hash[32]; + CryptoCore::sha256((unsigned char *)cs.ptr(), cs.length(), hash); + return String::hex_encode_buffer(hash, 32); +} + +Vector String::md5_buffer() const { + CharString cs = utf8(); + unsigned char hash[16]; + CryptoCore::md5((unsigned char *)cs.ptr(), cs.length(), hash); + + Vector ret; + ret.resize(16); + for (int i = 0; i < 16; i++) { + ret.write[i] = hash[i]; + } + return ret; +}; + +Vector String::sha1_buffer() const { + CharString cs = utf8(); + unsigned char hash[20]; + CryptoCore::sha1((unsigned char *)cs.ptr(), cs.length(), hash); + + Vector ret; + ret.resize(20); + for (int i = 0; i < 20; i++) { + ret.write[i] = hash[i]; + } + + return ret; +} + +Vector String::sha256_buffer() const { + CharString cs = utf8(); + unsigned char hash[32]; + CryptoCore::sha256((unsigned char *)cs.ptr(), cs.length(), hash); + + Vector ret; + ret.resize(32); + for (int i = 0; i < 32; i++) { + ret.write[i] = hash[i]; + } + return ret; +} + +bool String::is_abs_path() const { + if (length() > 1) { + return (operator[](0) == '/' || operator[](0) == '\\' || find(":/") != -1 || find(":\\") != -1); + } else if ((length()) == 1) { + return (operator[](0) == '/' || operator[](0) == '\\'); + } else { + return false; + } +} + +bool String::is_rel_path() const { + return !is_abs_path(); +} + +bool String::is_resource_file() const { + return begins_with("res://") && find("::") == -1; +} + +String String::path_to(const String &p_path) const { + String src = this->replace("\\", "/"); + String dst = p_path.replace("\\", "/"); + if (!src.ends_with("/")) { + src += "/"; + } + if (!dst.ends_with("/")) { + dst += "/"; + } + + String base; + + if (src.begins_with("res://") && dst.begins_with("res://")) { + base = "res:/"; + src = src.replace("res://", "/"); + dst = dst.replace("res://", "/"); + + } else if (src.begins_with("user://") && dst.begins_with("user://")) { + base = "user:/"; + src = src.replace("user://", "/"); + dst = dst.replace("user://", "/"); + + } else if (src.begins_with("/") && dst.begins_with("/")) { + //nothing + } else { + //dos style + String src_begin = src.get_slicec('/', 0); + String dst_begin = dst.get_slicec('/', 0); + + if (src_begin != dst_begin) { + return p_path; //impossible to do this + } + + base = src_begin; + src = src.substr(src_begin.length(), src.length()); + dst = dst.substr(dst_begin.length(), dst.length()); + } + + //remove leading and trailing slash and split + Vector src_dirs = src.substr(1, src.length() - 2).split("/"); + Vector dst_dirs = dst.substr(1, dst.length() - 2).split("/"); + + //find common parent + int common_parent = 0; + + while (true) { + if (src_dirs.size() == common_parent) { + break; + } + if (dst_dirs.size() == common_parent) { + break; + } + if (src_dirs[common_parent] != dst_dirs[common_parent]) { + break; + } + common_parent++; + } + + common_parent--; + + String dir; + + for (int i = src_dirs.size() - 1; i > common_parent; i--) { + dir += "../"; + } + + for (int i = common_parent + 1; i < dst_dirs.size(); i++) { + dir += dst_dirs[i] + "/"; + } + + if (dir.length() == 0) { + dir = "./"; + } + return dir; +} + +String String::path_to_file(const String &p_path) const { + // Don't get base dir for src, this is expected to be a dir already. + String src = this->replace("\\", "/"); + String dst = p_path.replace("\\", "/").get_base_dir(); + String rel = src.path_to(dst); + if (rel == dst) { // failed + return p_path; + } else { + return rel + p_path.get_file(); + } +} + +String String::get_base_dir() const { + int end = 0; + + // url scheme style base + int basepos = find("://"); + if (basepos != -1) { + end = basepos + 3; + } + + // windows top level directory base + if (end == 0) { + basepos = find(":/"); + if (basepos == -1) { + basepos = find(":\\"); + } + if (basepos != -1) { + end = basepos + 2; + } + } + + // Windows UNC network share path. + if (end == 0) { + if (is_network_share_path()) { + basepos = find("/", 2); + if (basepos == -1) { + basepos = find("\\", 2); + } + int servpos = find("/", basepos + 1); + if (servpos == -1) { + servpos = find("\\", basepos + 1); + } + if (servpos != -1) { + end = servpos + 1; + } + } + } + + // unix root directory base + if (end == 0) { + if (begins_with("/")) { + end = 1; + } + } + + String rs; + String base; + if (end != 0) { + rs = substr(end, length()); + base = substr(0, end); + } else { + rs = *this; + } + + int sep = MAX(rs.rfind("/"), rs.rfind("\\")); + if (sep == -1) { + return base; + } + + return base + rs.substr(0, sep); +} + +String String::get_file() const { + int sep = MAX(rfind("/"), rfind("\\")); + if (sep == -1) { + return *this; + } + + return substr(sep + 1, length()); +} + +String String::humanize_size(uint64_t p_size) { + uint64_t _div = 1; + Vector prefixes; + prefixes.push_back(RTR("B")); + prefixes.push_back(RTR("KiB")); + prefixes.push_back(RTR("MiB")); + prefixes.push_back(RTR("GiB")); + prefixes.push_back(RTR("TiB")); + prefixes.push_back(RTR("PiB")); + prefixes.push_back(RTR("EiB")); + + int prefix_idx = 0; + + while (prefix_idx < prefixes.size() - 1 && p_size > (_div * 1024)) { + _div *= 1024; + prefix_idx++; + } + + const int digits = prefix_idx > 0 ? _humanize_digits(p_size / _div) : 0; + const double divisor = prefix_idx > 0 ? _div : 1; + + return String::num(p_size / divisor).pad_decimals(digits) + " " + prefixes[prefix_idx]; +} + +String String::simplify_path() const { + String s = *this; + String drive; + if (s.begins_with("local://")) { + drive = "local://"; + s = s.substr(8, s.length()); + } else if (s.begins_with("res://")) { + drive = "res://"; + s = s.substr(6, s.length()); + } else if (s.begins_with("user://")) { + drive = "user://"; + s = s.substr(7, s.length()); + } else if (is_network_share_path()) { + drive = s.substr(0, 2); + s = s.substr(2, s.length() - 2); + } else if (s.begins_with("/") || s.begins_with("\\")) { + drive = s.substr(0, 1); + s = s.substr(1, s.length() - 1); + } else { + int p = s.find(":/"); + if (p == -1) { + p = s.find(":\\"); + } + if (p != -1 && p < s.find("/")) { + drive = s.substr(0, p + 2); + s = s.substr(p + 2, s.length()); + } + } + + s = s.replace("\\", "/"); + while (true) { // in case of using 2 or more slash + String compare = s.replace("//", "/"); + if (s == compare) { + break; + } else { + s = compare; + } + } + Vector dirs = s.split("/", false); + + for (int i = 0; i < dirs.size(); i++) { + String d = dirs[i]; + if (d == ".") { + dirs.remove(i); + i--; + } else if (d == "..") { + if (i == 0) { + dirs.remove(i); + i--; + } else { + dirs.remove(i); + dirs.remove(i - 1); + i -= 2; + } + } + } + + s = ""; + + for (int i = 0; i < dirs.size(); i++) { + if (i > 0) { + s += "/"; + } + s += dirs[i]; + } + + return drive + s; +} + +bool String::is_network_share_path() const { + return begins_with("//") || begins_with("\\\\"); +} + +String String::append_path(const char *path) const { + if (path[0] == '\0') { + return *this; + } + + String ret = *this; + int size = length(); + + if (size == 0) { + ret += path; + return ret; + } + + int sindex = 0; + char ch = path[sindex]; + while (ch == '/' || ch == '\\') { + if (ch == '\0') { + return ret; + } + + ch = path[++sindex]; + } + + // /////folder + // ^ (sindex) + + if (ret.ends_with("/") || ret.ends_with("\\")) { + ret += &path[sindex]; + } else { + if (sindex > 0) { + ret += '/'; + ret += &path[sindex - 1]; + } else { + ret += '/'; + ret += &path[sindex]; + } + } + + return ret; +} + +String String::append_path(const String &path) const { + if (path.length() == 0) { + return *this; + } + + int size = length(); + + if (size == 0) { + return path; + } + + int sindex = 0; + int ts = path.size() - 1; + char ch = path[sindex]; + while (ch == '/' || ch == '\\') { + if (sindex == ts) { + return *this; + } + + ch = path[++sindex]; + } + + String ret = *this; + + // /////folder + // ^ (sindex) + + if (ret.ends_with("/") || ret.ends_with("\\")) { + ret += &path[sindex]; + } else { + if (sindex > 0) { + ret += '/'; + ret += &path[sindex - 1]; + } else { + ret += '/'; + ret += &path[sindex]; + } + } + + return ret; +} + +String String::path_clean_end_slash() const { + // _size > 1, so if root is given ("/"), it will not be removed + + String ret = *this; + + while (ret.length() > 1 && (ret.ends_with("/") || ret.ends_with("\\"))) { + ret.resize(ret.length()); + } + + return ret; +} +String String::path_ensure_end_slash() const { + // Don't add if empty string, as it would make it root on linux, which can easily become a serious bug + + String ret = *this; + + if (ret.length() == 0) { + return ret; + } + + if (!(ret.ends_with("/") || ret.ends_with("\\"))) { + ret += "/"; + } + + return ret; +} + +String String::path_get_prev_dir() const { + int size = length(); + + if (size == 0) { + return "/"; + } + + int seind = size - 1; + while (seind > 0 && (operator[](seind) == '/' || operator[](seind) == '\\')) { + --seind; + } + + if (seind == 0) { + // ///////// + // or + // a/////// + // no prev dir + + return "/"; + } + + // fol/fol2/fol3// + // ^ (seind) + + while (seind > 0 && (operator[](seind) != '/' && operator[](seind) != '\\')) { + --seind; + } + + // fol/fol2/fol3// + // ^ (seind) + + //--seind; + + if (seind <= 0) { + return "/"; + } + + return substr_index(0, seind); +} + +String String::xml_escape(bool p_escape_quotes) const { + String str = *this; + str = str.replace("&", "&"); + str = str.replace("<", "<"); + str = str.replace(">", ">"); + if (p_escape_quotes) { + str = str.replace("'", "'"); + str = str.replace("\"", """); + } + /* + for (int i=1;i<32;i++) { + + char chr[2]={i,0}; + str=str.replace(chr,"&#"+String::num(i)+";"); + }*/ + return str; +} + +static _FORCE_INLINE_ int _xml_unescape(const CharType *p_src, int p_src_len, CharType *p_dst) { + int len = 0; + while (p_src_len) { + if (*p_src == '&') { + int eat = 0; + + if (p_src_len >= 4 && p_src[1] == '#') { + CharType c = 0; + bool overflow = false; + if (p_src[2] == 'x') { + // Hex entity &#x; + for (int i = 3; i < p_src_len; i++) { + eat = i + 1; + CharType ct = p_src[i]; + if (ct == ';') { + break; + } else if (ct >= '0' && ct <= '9') { + ct = ct - '0'; + } else if (ct >= 'a' && ct <= 'f') { + ct = (ct - 'a') + 10; + } else if (ct >= 'A' && ct <= 'F') { + ct = (ct - 'A') + 10; + } else { + break; + } + if (c > (WCHAR_MAX >> 4)) { + overflow = true; + break; + } + c <<= 4; + c |= ct; + } + } else { + // Decimal entity &#; + for (int i = 2; i < p_src_len; i++) { + eat = i + 1; + CharType ct = p_src[i]; + if (ct == ';' || ct < '0' || ct > '9') { + break; + } + } + if (p_src[eat - 1] == ';') { + int64_t val = String::to_int(p_src + 2, eat - 3); + if (val > 0 && val <= WCHAR_MAX) { + c = (CharType)val; + } else { + overflow = true; + } + } + } + + // Value must be non-zero, in the range of CharType, + // actually end with ';'. If invalid, leave the entity as-is + if (c == '\0' || overflow || p_src[eat - 1] != ';') { + eat = 1; + c = *p_src; + } + if (p_dst) { + *p_dst = c; + } + } else if (p_src_len >= 4 && p_src[1] == 'g' && p_src[2] == 't' && p_src[3] == ';') { + if (p_dst) { + *p_dst = '>'; + } + eat = 4; + } else if (p_src_len >= 4 && p_src[1] == 'l' && p_src[2] == 't' && p_src[3] == ';') { + if (p_dst) { + *p_dst = '<'; + } + eat = 4; + } else if (p_src_len >= 5 && p_src[1] == 'a' && p_src[2] == 'm' && p_src[3] == 'p' && p_src[4] == ';') { + if (p_dst) { + *p_dst = '&'; + } + eat = 5; + } else if (p_src_len >= 6 && p_src[1] == 'q' && p_src[2] == 'u' && p_src[3] == 'o' && p_src[4] == 't' && p_src[5] == ';') { + if (p_dst) { + *p_dst = '"'; + } + eat = 6; + } else if (p_src_len >= 6 && p_src[1] == 'a' && p_src[2] == 'p' && p_src[3] == 'o' && p_src[4] == 's' && p_src[5] == ';') { + if (p_dst) { + *p_dst = '\''; + } + eat = 6; + } else { + if (p_dst) { + *p_dst = *p_src; + } + eat = 1; + } + + if (p_dst) { + p_dst++; + } + + len++; + p_src += eat; + p_src_len -= eat; + } else { + if (p_dst) { + *p_dst = *p_src; + p_dst++; + } + len++; + p_src++; + p_src_len--; + } + } + + return len; +} + +String String::xml_unescape() const { + String str; + int l = length(); + int len = _xml_unescape(get_data(), l, nullptr); + if (len == 0) { + return String(); + } + str.resize(len + 1); + _xml_unescape(get_data(), l, str.ptrw()); + str[len] = 0; + return str; +} + +String String::http_escape() const { + const CharString temp = utf8(); + String res; + for (int i = 0; i < temp.length(); ++i) { + uint8_t ord = temp[i]; + if (ord == '.' || ord == '-' || ord == '_' || ord == '~' || + (ord >= 'a' && ord <= 'z') || + (ord >= 'A' && ord <= 'Z') || + (ord >= '0' && ord <= '9')) { + res += ord; + } else { + char p[4] = { '%', 0, 0, 0 }; + static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + p[1] = hex[ord >> 4]; + p[2] = hex[ord & 0xF]; + res += p; + } + } + return res; +} + +String String::http_unescape() const { + String res; + for (int i = 0; i < length(); ++i) { + if (ord_at(i) == '%' && i + 2 < length()) { + CharType ord1 = ord_at(i + 1); + if ((ord1 >= '0' && ord1 <= '9') || (ord1 >= 'A' && ord1 <= 'Z')) { + CharType ord2 = ord_at(i + 2); + if ((ord2 >= '0' && ord2 <= '9') || (ord2 >= 'A' && ord2 <= 'Z')) { + char bytes[3] = { (char)ord1, (char)ord2, 0 }; + res += (char)strtol(bytes, nullptr, 16); + i += 2; + } + } else { + res += ord_at(i); + } + } else { + res += ord_at(i); + } + } + return String::utf8(res.ascii()); +} + +String String::uri_encode() const { + const CharString temp = utf8(); + String res; + for (int i = 0; i < temp.length(); ++i) { + uint8_t ord = temp[i]; + if (ord == '.' || ord == '-' || ord == '~' || is_ascii_identifier_char(ord)) { + res += ord; + } else { + char p[4] = { '%', 0, 0, 0 }; + static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + p[1] = hex[ord >> 4]; + p[2] = hex[ord & 0xF]; + res += p; + } + } + return res; +} + +String String::uri_decode() const { + CharString src = utf8(); + CharString res; + for (int i = 0; i < src.length(); ++i) { + if (src[i] == '%' && i + 2 < src.length()) { + char ord1 = src[i + 1]; + if (is_digit(ord1) || is_ascii_upper_case(ord1)) { + char ord2 = src[i + 2]; + if (is_digit(ord2) || is_ascii_upper_case(ord2)) { + char bytes[3] = { (char)ord1, (char)ord2, 0 }; + res += (char)strtol(bytes, nullptr, 16); + i += 2; + } + } else { + res += src[i]; + } + } else if (src[i] == '+') { + res += ' '; + } else { + res += src[i]; + } + } + return String::utf8(res); +} + +String String::c_escape() const { + String escaped = *this; + escaped = escaped.replace("\\", "\\\\"); + escaped = escaped.replace("\a", "\\a"); + escaped = escaped.replace("\b", "\\b"); + escaped = escaped.replace("\f", "\\f"); + escaped = escaped.replace("\n", "\\n"); + escaped = escaped.replace("\r", "\\r"); + escaped = escaped.replace("\t", "\\t"); + escaped = escaped.replace("\v", "\\v"); + escaped = escaped.replace("\'", "\\'"); + escaped = escaped.replace("\?", "\\?"); + escaped = escaped.replace("\"", "\\\""); + + return escaped; +} + +String String::c_escape_multiline() const { + String escaped = *this; + escaped = escaped.replace("\\", "\\\\"); + escaped = escaped.replace("\"", "\\\""); + + return escaped; +} + +String String::c_unescape() const { + String escaped = *this; + escaped = escaped.replace("\\a", "\a"); + escaped = escaped.replace("\\b", "\b"); + escaped = escaped.replace("\\f", "\f"); + escaped = escaped.replace("\\n", "\n"); + escaped = escaped.replace("\\r", "\r"); + escaped = escaped.replace("\\t", "\t"); + escaped = escaped.replace("\\v", "\v"); + escaped = escaped.replace("\\'", "\'"); + escaped = escaped.replace("\\\"", "\""); + escaped = escaped.replace("\\?", "\?"); + escaped = escaped.replace("\\\\", "\\"); + + return escaped; +} + +String String::json_escape() const { + String escaped = *this; + escaped = escaped.replace("\\", "\\\\"); + escaped = escaped.replace("\b", "\\b"); + escaped = escaped.replace("\f", "\\f"); + escaped = escaped.replace("\n", "\\n"); + escaped = escaped.replace("\r", "\\r"); + escaped = escaped.replace("\t", "\\t"); + escaped = escaped.replace("\v", "\\v"); + escaped = escaped.replace("\"", "\\\""); + + return escaped; +} + +//kind of poor should be rewritten properly +String String::word_wrap(int p_chars_per_line) const { + int from = 0; + int last_space = 0; + String ret; + for (int i = 0; i < length(); i++) { + if (i - from >= p_chars_per_line) { + if (last_space == -1) { + ret += substr(from, i - from + 1) + "\n"; + } else { + ret += substr(from, last_space - from) + "\n"; + i = last_space; //rewind + } + from = i + 1; + last_space = -1; + } else if (operator[](i) == ' ' || operator[](i) == '\t') { + last_space = i; + } else if (operator[](i) == '\n') { + ret += substr(from, i - from) + "\n"; + from = i + 1; + last_space = -1; + } + } + + if (from < length()) { + ret += substr(from, length()); + } + + return ret; +} + +Error String::parse_url(String &r_scheme, String &r_host, int &r_port, String &r_path) const { + // Splits the URL into scheme, host, port, path. Strip credentials when present. + String base = *this; + r_scheme = ""; + r_host = ""; + r_port = 0; + r_path = ""; + int pos = base.find("://"); + // Scheme + if (pos != -1) { + r_scheme = base.substr(0, pos + 3).to_lower(); + base = base.substr(pos + 3, base.length() - pos - 3); + } + pos = base.find("/"); + // Path + if (pos != -1) { + r_path = base.substr(pos, base.length() - pos); + base = base.substr(0, pos); + } + // Host + pos = base.find("@"); + if (pos != -1) { + // Strip credentials + base = base.substr(pos + 1, base.length() - pos - 1); + } + if (base.begins_with("[")) { + // Literal IPv6 + pos = base.rfind("]"); + if (pos == -1) { + return ERR_INVALID_PARAMETER; + } + r_host = base.substr(1, pos - 1); + base = base.substr(pos + 1, base.length() - pos - 1); + } else { + // Anything else + if (base.get_slice_count(":") > 2) { + return ERR_INVALID_PARAMETER; + } + pos = base.rfind(":"); + if (pos == -1) { + r_host = base; + base = ""; + } else { + r_host = base.substr(0, pos); + base = base.substr(pos, base.length() - pos); + } + } + if (r_host.empty()) { + return ERR_INVALID_PARAMETER; + } + r_host = r_host.to_lower(); + // Port + if (base.begins_with(":")) { + base = base.substr(1, base.length() - 1); + if (!base.is_valid_integer()) { + return ERR_INVALID_PARAMETER; + } + r_port = base.to_int(); + if (r_port < 1 || r_port > 65535) { + return ERR_INVALID_PARAMETER; + } + } + return OK; +} + +String String::percent_encode() const { + CharString cs = utf8(); + String encoded; + for (int i = 0; i < cs.length(); i++) { + uint8_t c = cs[i]; + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '~' || c == '.') { + char p[2] = { (char)c, 0 }; + encoded += p; + } else { + char p[4] = { '%', 0, 0, 0 }; + static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + p[1] = hex[c >> 4]; + p[2] = hex[c & 0xF]; + encoded += p; + } + } + + return encoded; +} +String String::percent_decode() const { + CharString pe; + + CharString cs = utf8(); + for (int i = 0; i < cs.length(); i++) { + uint8_t c = cs[i]; + if (c == '%' && i < length() - 2) { + uint8_t a = LOWERCASE(cs[i + 1]); + uint8_t b = LOWERCASE(cs[i + 2]); + + if (a >= '0' && a <= '9') { + c = (a - '0') << 4; + } else if (a >= 'a' && a <= 'f') { + c = (a - 'a' + 10) << 4; + } else { + continue; + } + + uint8_t d = 0; + + if (b >= '0' && b <= '9') { + d = (b - '0'); + } else if (b >= 'a' && b <= 'f') { + d = (b - 'a' + 10); + } else { + continue; + } + c += d; + i += 2; + } + pe += c; + } + + return String::utf8(pe.ptr()); +} + +String String::property_name_encode() const { + // Escape and quote strings with extended ASCII or further Unicode characters + // as well as '"', '=' or ' ' (32) + const CharType *cstr = get_data(); + for (int i = 0; cstr[i]; i++) { + if (cstr[i] == '=' || cstr[i] == '"' || cstr[i] == ';' || cstr[i] == '[' || cstr[i] == ']' || cstr[i] < 33 || cstr[i] > 126) { + return "\"" + c_escape_multiline() + "\""; + } + } + // Keep as is + return *this; +} + +// Changes made to the set of invalid characters must also be reflected in the String documentation. +const String String::invalid_node_name_characters = ". : @ / \" %"; + +String String::validate_node_name() const { + Vector chars = String::invalid_node_name_characters.split(" "); + String name = this->replace(chars[0], ""); + for (int i = 1; i < chars.size(); i++) { + name = name.replace(chars[i], ""); + } + return name; +} + +bool String::is_valid_identifier() const { + int len = length(); + + if (len == 0) { + return false; + } + + const CharType *str = &operator[](0); + + for (int i = 0; i < len; i++) { + if (i == 0) { + if (str[0] >= '0' && str[0] <= '9') { + return false; // no start with number plz + } + } + + bool valid_char = (str[i] >= '0' && str[i] <= '9') || (str[i] >= 'a' && str[i] <= 'z') || (str[i] >= 'A' && str[i] <= 'Z') || str[i] == '_'; + + if (!valid_char) { + return false; + } + } + + return true; +} + +bool String::is_valid_integer() const { + int len = length(); + + if (len == 0) { + return false; + } + + int from = 0; + if (len != 1 && (operator[](0) == '+' || operator[](0) == '-')) { + from++; + } + + for (int i = from; i < len; i++) { + if (operator[](i) < '0' || operator[](i) > '9') { + return false; // no start with number plz + } + } + + return true; +} + +bool String::is_valid_hex_number(bool p_with_prefix) const { + int len = length(); + + if (len == 0) { + return false; + } + + int from = 0; + if (len != 1 && (operator[](0) == '+' || operator[](0) == '-')) { + from++; + } + + if (p_with_prefix) { + if (len < 3) { + return false; + } + if (operator[](from) != '0' || operator[](from + 1) != 'x') { + return false; + } + from += 2; + } + + for (int i = from; i < len; i++) { + CharType c = operator[](i); + if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { + continue; + } + return false; + } + + return true; +}; + +bool String::is_valid_float() const { + int len = length(); + + if (len == 0) { + return false; + } + + int from = 0; + if (operator[](0) == '+' || operator[](0) == '-') { + from++; + } + + bool exponent_found = false; + bool period_found = false; + bool sign_found = false; + bool exponent_values_found = false; + bool numbers_found = false; + + for (int i = from; i < len; i++) { + if (operator[](i) >= '0' && operator[](i) <= '9') { + if (exponent_found) { + exponent_values_found = true; + } else { + numbers_found = true; + } + } else if (numbers_found && !exponent_found && operator[](i) == 'e') { + exponent_found = true; + } else if (!period_found && !exponent_found && operator[](i) == '.') { + period_found = true; + } else if ((operator[](i) == '-' || operator[](i) == '+') && exponent_found && !exponent_values_found && !sign_found) { + sign_found = true; + } else { + return false; // no start with number plz + } + } + + return numbers_found; +} + +bool String::is_valid_bool() const { + int size = length(); + + if (size == 1) { + CharType c = ptr()[0]; + + if (c == '0') { + return true; + } else if (c == '1') { + return true; + } + + return false; + } else if (size == 4) { + String l = to_lower(); + const CharType *p = l.ptr(); + + if (p[0] == 't' && p[1] == 'r' && p[2] == 'u' && p[3] == 'e') { + return true; + } else { + return false; + } + } else if (size == 5) { + String l = to_lower(); + const CharType *p = l.ptr(); + + if (p[0] == 'f' && p[1] == 'a' && p[2] == 'l' && p[3] == 's' && p[3] == 'e') { + return true; + } else { + return false; + } + } + + return false; +} + +bool String::is_valid_unsigned_integer() const { + int len = length(); + + if (len == 0) { + return false; + } + + int from = 0; + if (len != 1 && (operator[](0) == '+')) { + from++; + } + + for (int i = from; i < len; i++) { + if (operator[](i) < '0' || operator[](i) > '9') { + return false; // no start with number plz + } + } + + return true; +} + +bool String::is_valid_html_color() const { + return Color::html_is_valid(*this); +} + +bool String::is_valid_filename() const { + String stripped = strip_edges(); + if (*this != stripped) { + return false; + } + + if (stripped == String()) { + return false; + } + + return !(find(":") != -1 || find("/") != -1 || find("\\") != -1 || find("?") != -1 || find("*") != -1 || find("\"") != -1 || find("|") != -1 || find("%") != -1 || find("<") != -1 || find(">") != -1); +} + +bool String::is_valid_ip_address() const { + if (find(":") >= 0) { + Vector ip = split(":"); + for (int i = 0; i < ip.size(); i++) { + String n = ip[i]; + if (n.empty()) { + continue; + } + if (n.is_valid_hex_number(false)) { + int nint = n.hex_to_int(false); + if (nint < 0 || nint > 0xffff) { + return false; + } + continue; + }; + if (!n.is_valid_ip_address()) { + return false; + } + }; + + } else { + Vector ip = split("."); + if (ip.size() != 4) { + return false; + } + for (int i = 0; i < ip.size(); i++) { + String n = ip[i]; + if (!n.is_valid_integer()) { + return false; + } + int val = n.to_int(); + if (val < 0 || val > 255) { + return false; + } + } + }; + + return true; +} + +Vector String::to_ascii_buffer() const { + const String *s = this; + if (s->empty()) { + return Vector(); + } + CharString charstr = s->ascii(); + + Vector retval; + size_t len = charstr.length(); + retval.resize(len); + uint8_t *w = retval.ptrw(); + memcpy(w, charstr.ptr(), len); + + return retval; +} + +Vector String::to_utf8_buffer() const { + const String *s = this; + if (s->empty()) { + return Vector(); + } + CharString charstr = s->utf8(); + + Vector retval; + size_t len = charstr.length(); + retval.resize(len); + uint8_t *w = retval.ptrw(); + memcpy(w, charstr.ptr(), len); + + return retval; +} + +Vector String::to_utf16_buffer() const { + const String *s = this; + if (s->empty()) { + return Vector(); + } + Char16String charstr = s->utf16(); + + Vector retval; + size_t len = charstr.length() * sizeof(char16_t); + retval.resize(len); + uint8_t *w = retval.ptrw(); + memcpy(w, (const void *)charstr.ptr(), len); + + return retval; +} + +Vector String::to_utf32_buffer() const { + const String *s = this; + if (s->empty()) { + return Vector(); + } + + Vector retval; + size_t len = s->length() * sizeof(CharType); + retval.resize(len); + uint8_t *w = retval.ptrw(); + memcpy(w, (const void *)s->ptr(), len); + + return retval; +} + String::String(const char *p_str) { copy_from(p_str); } +String::String(const wchar_t *p_str) { + copy_from(p_str); +} + +String::String(const CharType *p_str) { + copy_from(p_str); +} + +String::String(const char *p_str, int p_clip_to_len) { + copy_from(p_str, p_clip_to_len); +} + +String::String(const wchar_t *p_str, int p_clip_to_len) { + copy_from(p_str, p_clip_to_len); +} + String::String(const CharType *p_str, int p_clip_to_len) { copy_from(p_str, p_clip_to_len); } @@ -1725,10 +4910,11 @@ String::String(const StrRange &p_range) { if (!p_range.c_str) { return; } - copy_from(p_range.c_str, p_range.len); } +// Other conversions + int String::hex_to_int(bool p_with_prefix) const { int len = length(); ERR_FAIL_COND_V_MSG(p_with_prefix ? len < 3 : len == 0, 0, String("Invalid hexadecimal notation length in string ") + (p_with_prefix ? "with" : "without") + " prefix \"" + *this + "\"."); @@ -1846,169 +5032,6 @@ int64_t String::bin_to_int64(bool p_with_prefix) const { return binary * sign; } -int String::to_int() const { - if (length() == 0) { - return 0; - } - - int to = (find(".") >= 0) ? find(".") : length(); - - int integer = 0; - int sign = 1; - - for (int i = 0; i < to; i++) { - CharType c = operator[](i); - if (c >= '0' && c <= '9') { - bool overflow = (integer > INT32_MAX / 10) || (integer == INT32_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8'))); - ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT32_MAX : INT32_MIN, "Cannot represent " + *this + " as a 32-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small.")); - integer *= 10; - integer += c - '0'; - - } else if (integer == 0 && c == '-') { - sign = -sign; - } - } - - return integer * sign; -} - -bool String::to_bool() const { - if (length() == 0) { - return false; - } - - if (is_numeric()) { - return to_int() != 0; - } - - return to_lower() == "true"; -} - -uint32_t String::to_uint() const { - if (is_numeric()) { - return static_cast(to_int()); - } - - return 0; -} - -int64_t String::to_int64() const { - if (length() == 0) { - return 0; - } - - int to = (find(".") >= 0) ? find(".") : length(); - - int64_t integer = 0; - int64_t sign = 1; - - for (int i = 0; i < to; i++) { - CharType c = operator[](i); - if (c >= '0' && c <= '9') { - bool overflow = (integer > INT64_MAX / 10) || (integer == INT64_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8'))); - ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + *this + " as a 64-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small.")); - integer *= 10; - integer += c - '0'; - - } else if (integer == 0 && c == '-') { - sign = -sign; - } - } - - return integer * sign; -} - -int String::to_int(const char *p_str, int p_len) { - int to = 0; - if (p_len >= 0) { - to = p_len; - } else { - while (p_str[to] != 0 && p_str[to] != '.') { - to++; - } - } - - int integer = 0; - int sign = 1; - - for (int i = 0; i < to; i++) { - char c = p_str[i]; - if (c >= '0' && c <= '9') { - bool overflow = (integer > INT32_MAX / 10) || (integer == INT32_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8'))); - ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT32_MAX : INT32_MIN, "Cannot represent " + String(p_str).substr(0, to) + " as a 32-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small.")); - integer *= 10; - integer += c - '0'; - - } else if (c == '-' && integer == 0) { - sign = -sign; - } else if (c != ' ') { - break; - } - } - - return integer * sign; -} - -bool String::is_numeric() const { - if (length() == 0) { - return false; - }; - - int s = 0; - if (operator[](0) == '-') { - ++s; - } - bool dot = false; - for (int i = s; i < length(); i++) { - CharType c = operator[](i); - if (c == '.') { - if (dot) { - return false; - }; - dot = true; - } - if (c < '0' || c > '9') { - return false; - }; - }; - - return true; // TODO: Use the parser below for this instead -}; - -bool String::is_zero() const { - int size = length(); - - if (size == 0) { - return false; - } - - int starti = 0; - - if (operator[](0) == '-') { - starti += 1; - } - - bool had_dot = false; - for (int i = starti; i < size; ++i) { - CharType c = operator[](i); - - if (c == '.') { - if (!had_dot) { - had_dot = true; - continue; - } else { - return false; - } - } - - if (c != '0') { - return false; - } - } - - return true; -} - template static double built_in_strtod( /* A decimal ASCII floating-point number, @@ -2226,29 +5249,157 @@ done: return fraction; } -#define READING_SIGN 0 -#define READING_INT 1 -#define READING_DEC 2 -#define READING_EXP 3 -#define READING_DONE 4 +double String::to_double() const { + if (empty()) { + return 0; + } -double String::to_double(const char *p_str) { -#ifndef NO_USE_STDLIB - return built_in_strtod(p_str); -#else - return built_in_strtod(p_str); -#endif + return built_in_strtod(get_data()); } float String::to_float() const { - return to_double(); + if (empty()) { + return 0; + } + + return built_in_strtod(get_data()); } -double String::to_double(const CharType *p_str, const CharType **r_end) { - return built_in_strtod(p_str, (CharType **)r_end); +int String::to_int() const { + if (length() == 0) { + return 0; + } + + int to = (find(".") >= 0) ? find(".") : length(); + + int integer = 0; + int sign = 1; + + for (int i = 0; i < to; i++) { + CharType c = operator[](i); + if (c >= '0' && c <= '9') { + bool overflow = (integer > INT32_MAX / 10) || (integer == INT32_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8'))); + ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT32_MAX : INT32_MIN, "Cannot represent " + *this + " as a 32-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small.")); + integer *= 10; + integer += c - '0'; + + } else if (integer == 0 && c == '-') { + sign = -sign; + } + } + + return integer * sign; } -int64_t String::to_int(const CharType *p_str, int p_len) { +bool String::to_bool() const { + if (length() == 0) { + return false; + } + + if (is_numeric()) { + return to_int() != 0; + } + + return to_lower() == "true"; +} + +uint32_t String::to_uint() const { + if (is_numeric()) { + return static_cast(to_int()); + } + + return 0; +} + +int64_t String::to_int64() const { + if (length() == 0) { + return 0; + } + + int to = (find(".") >= 0) ? find(".") : length(); + + int64_t integer = 0; + int64_t sign = 1; + + for (int i = 0; i < to; i++) { + CharType c = operator[](i); + if (c >= '0' && c <= '9') { + bool overflow = (integer > INT64_MAX / 10) || (integer == INT64_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8'))); + ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + *this + " as a 64-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small.")); + integer *= 10; + integer += c - '0'; + + } else if (integer == 0 && c == '-') { + sign = -sign; + } + } + + return integer * sign; +} + +int64_t String::to_int(const char *p_str, int p_len) { + int to = 0; + if (p_len >= 0) { + to = p_len; + } else { + while (p_str[to] != 0 && p_str[to] != '.') { + to++; + } + } + + int64_t integer = 0; + int64_t sign = 1; + + for (int i = 0; i < to; i++) { + char c = p_str[i]; + if (is_digit(c)) { + bool overflow = (integer > INT64_MAX / 10) || (integer == INT64_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8'))); + ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + String(p_str).substr(0, to) + " as a 64-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small.")); + integer *= 10; + integer += c - '0'; + + } else if (c == '-' && integer == 0) { + sign = -sign; + } else if (c != ' ') { + break; + } + } + + return integer * sign; +} + +int64_t String::to_int(const wchar_t *p_str, int p_len) { + int to = 0; + if (p_len >= 0) { + to = p_len; + } else { + while (p_str[to] != 0 && p_str[to] != '.') { + to++; + } + } + + int64_t integer = 0; + int64_t sign = 1; + + for (int i = 0; i < to; i++) { + wchar_t c = p_str[i]; + if (is_digit(c)) { + bool overflow = (integer > INT64_MAX / 10) || (integer == INT64_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8'))); + ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + String(p_str).substr(0, to) + " as a 64-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small.")); + integer *= 10; + integer += c - '0'; + + } else if (c == '-' && integer == 0) { + sign = -sign; + } else if (c != ' ') { + break; + } + } + + return integer * sign; +} + +int64_t String::to_int(const CharType *p_str, int p_len, bool p_clamp) { if (p_len == 0 || !p_str[0]) { return 0; } @@ -2265,7 +5416,7 @@ int64_t String::to_int(const CharType *p_str, int p_len) { CharType c = *(str++); switch (reading) { case READING_SIGN: { - if (c >= '0' && c <= '9') { + if (is_digit(c)) { reading = READING_INT; // let it fallthrough } else if (c == '-') { @@ -2279,16 +5430,25 @@ int64_t String::to_int(const CharType *p_str, int p_len) { } else { break; } + [[fallthrough]]; } case READING_INT: { - if (c >= '0' && c <= '9') { + if (is_digit(c)) { if (integer > INT64_MAX / 10) { String number(""); str = p_str; while (*str && str != limit) { number += *(str++); } - ERR_FAIL_V_MSG(sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + number + " as a 64-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small.")); + if (p_clamp) { + if (sign == 1) { + return INT64_MAX; + } else { + return INT64_MIN; + } + } else { + ERR_FAIL_V_MSG(sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + number + " as a 64-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small.")); + } } integer *= 10; integer += c - '0'; @@ -2303,692 +5463,26 @@ int64_t String::to_int(const CharType *p_str, int p_len) { return sign * integer; } -double String::to_double() const { - if (empty()) { - return 0; - } -#ifndef NO_USE_STDLIB - return built_in_strtod(c_str()); -//return wcstod(c_str(),NULL); DOES NOT WORK ON ANDROID :( -#else - return built_in_strtod(c_str()); -#endif +double String::to_float(const char *p_str) { + return built_in_strtod(p_str); } -bool operator==(const char *p_chr, const String &p_str) { - return p_str == p_chr; +double String::to_float(const wchar_t *p_str, const wchar_t **r_end) { + return built_in_strtod(p_str, (wchar_t **)r_end); +} +double String::to_float(const CharType *p_str, const CharType **r_end) { + return built_in_strtod(p_str, (CharType **)r_end); } -String operator+(const char *p_chr, const String &p_str) { - String tmp = p_chr; - tmp += p_str; - return tmp; -} -String operator+(CharType p_chr, const String &p_str) { - return (String::chr(p_chr) + p_str); +double String::to_double(const char *p_str) { + return built_in_strtod(p_str); } -uint32_t String::hash(const char *p_cstr) { - uint32_t hashv = 5381; - uint32_t c; - - while ((c = *p_cstr++)) { - hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ - } - - return hashv; +double String::to_double(const wchar_t *p_str, const wchar_t **r_end) { + return built_in_strtod(p_str, (wchar_t **)r_end); } - -uint32_t String::hash(const char *p_cstr, int p_len) { - uint32_t hashv = 5381; - for (int i = 0; i < p_len; i++) { - hashv = ((hashv << 5) + hashv) + p_cstr[i]; /* hash * 33 + c */ - } - - return hashv; -} - -uint32_t String::hash(const CharType *p_cstr, int p_len) { - uint32_t hashv = 5381; - for (int i = 0; i < p_len; i++) { - hashv = ((hashv << 5) + hashv) + p_cstr[i]; /* hash * 33 + c */ - } - - return hashv; -} - -uint32_t String::hash(const CharType *p_cstr) { - uint32_t hashv = 5381; - uint32_t c; - - while ((c = *p_cstr++)) { - hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ - } - - return hashv; -} - -uint32_t String::hash() const { - /* simple djb2 hashing */ - - const CharType *chr = c_str(); - uint32_t hashv = 5381; - uint32_t c; - - while ((c = *chr++)) { - hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ - } - - return hashv; -} - -uint64_t String::hash64() const { - /* simple djb2 hashing */ - - const CharType *chr = c_str(); - uint64_t hashv = 5381; - uint64_t c; - - while ((c = *chr++)) { - hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ - } - - return hashv; -} - -String String::md5_text() const { - CharString cs = utf8(); - unsigned char hash[16]; - CryptoCore::md5((unsigned char *)cs.ptr(), cs.length(), hash); - return String::hex_encode_buffer(hash, 16); -} - -String String::sha1_text() const { - CharString cs = utf8(); - unsigned char hash[20]; - CryptoCore::sha1((unsigned char *)cs.ptr(), cs.length(), hash); - return String::hex_encode_buffer(hash, 20); -} - -String String::sha256_text() const { - CharString cs = utf8(); - unsigned char hash[32]; - CryptoCore::sha256((unsigned char *)cs.ptr(), cs.length(), hash); - return String::hex_encode_buffer(hash, 32); -} - -Vector String::md5_buffer() const { - CharString cs = utf8(); - unsigned char hash[16]; - CryptoCore::md5((unsigned char *)cs.ptr(), cs.length(), hash); - - Vector ret; - ret.resize(16); - for (int i = 0; i < 16; i++) { - ret.write[i] = hash[i]; - } - return ret; -}; - -Vector String::sha1_buffer() const { - CharString cs = utf8(); - unsigned char hash[20]; - CryptoCore::sha1((unsigned char *)cs.ptr(), cs.length(), hash); - - Vector ret; - ret.resize(20); - for (int i = 0; i < 20; i++) { - ret.write[i] = hash[i]; - } - - return ret; -} - -Vector String::sha256_buffer() const { - CharString cs = utf8(); - unsigned char hash[32]; - CryptoCore::sha256((unsigned char *)cs.ptr(), cs.length(), hash); - - Vector ret; - ret.resize(32); - for (int i = 0; i < 32; i++) { - ret.write[i] = hash[i]; - } - return ret; -} - -String String::insert(int p_at_pos, const String &p_string) const { - if (p_at_pos < 0) { - return *this; - } - - if (p_at_pos > length()) { - p_at_pos = length(); - } - - String pre; - if (p_at_pos > 0) { - pre = substr(0, p_at_pos); - } - - String post; - if (p_at_pos < length()) { - post = substr(p_at_pos, length() - p_at_pos); - } - - return pre + p_string + post; -} -String String::substr(int p_from, int p_chars) const { - if (p_chars == -1) { - p_chars = length() - p_from; - } - - if (empty() || p_from < 0 || p_from >= length() || p_chars <= 0) { - return ""; - } - - if ((p_from + p_chars) > length()) { - p_chars = length() - p_from; - } - - if (p_from == 0 && p_chars >= length()) { - return String(*this); - } - - String s = String(); - s.copy_from_unchecked(&c_str()[p_from], p_chars); - return s; -} - -String String::substr_index(const int start_index, const int end_index) const { - int s = length(); - - if (start_index < 0 || start_index >= s || end_index < 0 || start_index >= s) { - return ""; - } - - if (start_index > end_index) { - return ""; - } - - return substr(start_index, end_index - start_index); -} - -int String::find_last(const String &p_str) const { - return rfind(p_str); -} - -int String::find(const String &p_str, int p_from) const { - if (p_from < 0) { - return -1; - } - - const int src_len = p_str.length(); - - const int len = length(); - - if (src_len == 0 || len == 0) { - return -1; // won't find anything! - } - - const CharType *src = c_str(); - const CharType *str = p_str.c_str(); - - for (int i = p_from; i <= (len - src_len); i++) { - bool found = true; - for (int j = 0; j < src_len; j++) { - int read_pos = i + j; - - if (read_pos >= len) { - ERR_PRINT("read_pos>=len"); - return -1; - }; - - if (src[read_pos] != str[j]) { - found = false; - break; - } - } - - if (found) { - return i; - } - } - - return -1; -} - -int String::find(const char *p_str, int p_from) const { - if (p_from < 0) { - return -1; - } - - const int len = length(); - - if (len == 0) { - return -1; // won't find anything! - } - - const CharType *src = c_str(); - - int src_len = 0; - while (p_str[src_len] != '\0') { - src_len++; - } - - if (src_len == 1) { - const char needle = p_str[0]; - - for (int i = p_from; i < len; i++) { - if (src[i] == needle) { - return i; - } - } - - } else { - for (int i = p_from; i <= (len - src_len); i++) { - bool found = true; - for (int j = 0; j < src_len; j++) { - int read_pos = i + j; - - if (read_pos >= len) { - ERR_PRINT("read_pos>=len"); - return -1; - }; - - if (src[read_pos] != p_str[j]) { - found = false; - break; - } - } - - if (found) { - return i; - } - } - } - - return -1; -} - -int String::find_char(const CharType &p_char, int p_from) const { - return _cowdata.find(p_char, p_from); -} - -int String::findmk(const Vector &p_keys, int p_from, int *r_key) const { - if (p_from < 0) { - return -1; - } - if (p_keys.size() == 0) { - return -1; - } - - //int src_len=p_str.length(); - const String *keys = &p_keys[0]; - int key_count = p_keys.size(); - int len = length(); - - if (len == 0) { - return -1; // won't find anything! - } - - const CharType *src = c_str(); - - for (int i = p_from; i < len; i++) { - bool found = true; - for (int k = 0; k < key_count; k++) { - found = true; - if (r_key) { - *r_key = k; - } - const CharType *cmp = keys[k].c_str(); - int l = keys[k].length(); - - for (int j = 0; j < l; j++) { - int read_pos = i + j; - - if (read_pos >= len) { - found = false; - break; - }; - - if (src[read_pos] != cmp[j]) { - found = false; - break; - } - } - if (found) { - break; - } - } - - if (found) { - return i; - } - } - - return -1; -} - -int String::findn(const String &p_str, int p_from) const { - if (p_from < 0) { - return -1; - } - - int src_len = p_str.length(); - - if (src_len == 0 || length() == 0) { - return -1; // won't find anything! - } - - const CharType *srcd = c_str(); - - for (int i = p_from; i <= (length() - src_len); i++) { - bool found = true; - for (int j = 0; j < src_len; j++) { - int read_pos = i + j; - - if (read_pos >= length()) { - ERR_PRINT("read_pos>=length()"); - return -1; - }; - - CharType src = _find_lower(srcd[read_pos]); - CharType dst = _find_lower(p_str[j]); - - if (src != dst) { - found = false; - break; - } - } - - if (found) { - return i; - } - } - - return -1; -} - -int String::rfind(const String &p_str, int p_from) const { - // establish a limit - int limit = length() - p_str.length(); - if (limit < 0) { - return -1; - } - - // establish a starting point - if (p_from < 0) { - p_from = limit; - } else if (p_from > limit) { - p_from = limit; - } - - int src_len = p_str.length(); - int len = length(); - - if (src_len == 0 || len == 0) { - return -1; // won't find anything! - } - - const CharType *src = c_str(); - - for (int i = p_from; i >= 0; i--) { - bool found = true; - for (int j = 0; j < src_len; j++) { - int read_pos = i + j; - - if (read_pos >= len) { - ERR_PRINT("read_pos>=len"); - return -1; - }; - - if (src[read_pos] != p_str[j]) { - found = false; - break; - } - } - - if (found) { - return i; - } - } - - return -1; -} -int String::rfindn(const String &p_str, int p_from) const { - // establish a limit - int limit = length() - p_str.length(); - if (limit < 0) { - return -1; - } - - // establish a starting point - if (p_from < 0) { - p_from = limit; - } else if (p_from > limit) { - p_from = limit; - } - - int src_len = p_str.length(); - int len = length(); - - if (src_len == 0 || len == 0) { - return -1; // won't find anything! - } - - const CharType *src = c_str(); - - for (int i = p_from; i >= 0; i--) { - bool found = true; - for (int j = 0; j < src_len; j++) { - int read_pos = i + j; - - if (read_pos >= len) { - ERR_PRINT("read_pos>=len"); - return -1; - }; - - CharType srcc = _find_lower(src[read_pos]); - CharType dstc = _find_lower(p_str[j]); - - if (srcc != dstc) { - found = false; - break; - } - } - - if (found) { - return i; - } - } - - return -1; -} - -int String::find_first_difference_index(const String &p_str) const { - const int olen = p_str.length(); - const int len = length(); - const int c = len < olen ? len : olen; - - const CharType *p = c_str(); - const CharType *op = p_str.c_str(); - - for (int i = 0; i < c; ++i) { - if (p[i] != op[i]) { - return i; - } - } - - return c; -} - -bool String::is_word_at(const int index, const char *p_str) const { - int size = length(); - - ERR_FAIL_INDEX_V(index, size, false); - - int i = 0; - - while (p_str[i] != '\0') { - int iind = index + i; - - if (iind >= size) { - return false; - } - - if (operator[](iind) != p_str[i]) { - return false; - } - - ++i; - } - - return true; -} -bool String::is_word_at(const int index, const String &p_str) const { - int size = length(); - - ERR_FAIL_INDEX_V(index, size, false); - - if (index + p_str.length() >= size) { - return false; - } - - for (int i = 0; i < p_str.length(); ++i) { - int iind = index + i; - - if (operator[](iind) != p_str[i]) { - return false; - } - } - - return true; -} - -bool String::ends_with(const String &p_string) const { - int l = p_string.length(); - if (l > length()) { - return false; - } - - if (l == 0) { - return true; - } - - const CharType *p = &p_string[0]; - const CharType *s = &operator[](length() - l); - - for (int i = 0; i < l; i++) { - if (p[i] != s[i]) { - return false; - } - } - - return true; -} - -bool String::begins_with(const String &p_string) const { - int l = p_string.length(); - if (l > length()) { - return false; - } - - if (l == 0) { - return true; - } - - const CharType *p = &p_string[0]; - const CharType *s = &operator[](0); - - for (int i = 0; i < l; i++) { - if (p[i] != s[i]) { - return false; - } - } - - return true; -} - -bool String::begins_with(const char *p_string) const { - int l = length(); - if (l == 0 || !p_string) { - return false; - } - - const CharType *str = &operator[](0); - int i = 0; - - while (*p_string && i < l) { - if (*p_string != str[i]) { - return false; - } - i++; - p_string++; - } - - return *p_string == 0; -} - -bool String::is_enclosed_in(const String &p_string) const { - return begins_with(p_string) && ends_with(p_string); -} - -bool String::is_subsequence_of(const String &p_string) const { - return _base_is_subsequence_of(p_string, false); -} - -bool String::is_subsequence_ofi(const String &p_string) const { - return _base_is_subsequence_of(p_string, true); -} - -bool String::is_quoted() const { - return is_enclosed_in("\"") || is_enclosed_in("'"); -} - -int String::_count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const { - if (p_string.empty()) { - return 0; - } - int len = length(); - int slen = p_string.length(); - if (len < slen) { - return 0; - } - String str; - if (p_from >= 0 && p_to >= 0) { - if (p_to == 0) { - p_to = len; - } else if (p_from >= p_to) { - return 0; - } - if (p_from == 0 && p_to == len) { - str = String(); - str.copy_from_unchecked(&c_str()[0], len); - } else { - str = substr(p_from, p_to - p_from); - } - } else { - return 0; - } - int c = 0; - int idx = -1; - do { - idx = p_case_insensitive ? str.findn(p_string) : str.find(p_string); - if (idx != -1) { - str = str.substr(idx + slen, str.length() - slen); - ++c; - } - } while (idx != -1); - return c; -} - -int String::count(const String &p_string, int p_from, int p_to) const { - return _count(p_string, p_from, p_to, false); -} - -int String::countn(const String &p_string, int p_from, int p_to) const { - return _count(p_string, p_from, p_to, true); +double String::to_double(const CharType *p_str, const CharType **r_end) { + return built_in_strtod(p_str, (CharType **)r_end); } bool String::_base_is_subsequence_of(const String &p_string, bool case_insensitive) const { @@ -3025,1559 +5519,54 @@ bool String::_base_is_subsequence_of(const String &p_string, bool case_insensiti return false; } -Vector String::bigrams() const { - int n_pairs = length() - 1; - Vector b; - if (n_pairs <= 0) { - return b; - } - b.resize(n_pairs); - for (int i = 0; i < n_pairs; i++) { - b.write[i] = substr(i, 2); - } - return b; +bool operator==(const char *p_chr, const String &p_str) { + return p_str == p_chr; } -// Similarity according to Sorensen-Dice coefficient -float String::similarity(const String &p_string) const { - if (operator==(p_string)) { - // Equal strings are totally similar - return 1.0f; - } - if (length() < 2 || p_string.length() < 2) { - // No way to calculate similarity without a single bigram - return 0.0f; - } - - Vector src_bigrams = bigrams(); - Vector tgt_bigrams = p_string.bigrams(); - - int src_size = src_bigrams.size(); - int tgt_size = tgt_bigrams.size(); - - float sum = src_size + tgt_size; - float inter = 0; - for (int i = 0; i < src_size; i++) { - for (int j = 0; j < tgt_size; j++) { - if (src_bigrams[i] == tgt_bigrams[j]) { - inter++; - break; - } - } - } - - return (2.0f * inter) / sum; -} - -static bool _wildcard_match(const CharType *p_pattern, const CharType *p_string, bool p_case_sensitive) { - switch (*p_pattern) { - case '\0': - return !*p_string; - case '*': - return _wildcard_match(p_pattern + 1, p_string, p_case_sensitive) || (*p_string && _wildcard_match(p_pattern, p_string + 1, p_case_sensitive)); - case '?': - return *p_string && (*p_string != '.') && _wildcard_match(p_pattern + 1, p_string + 1, p_case_sensitive); - default: - - return (p_case_sensitive ? (*p_string == *p_pattern) : (_find_upper(*p_string) == _find_upper(*p_pattern))) && _wildcard_match(p_pattern + 1, p_string + 1, p_case_sensitive); - } -} - -bool String::match(const String &p_wildcard) const { - if (!p_wildcard.length() || !length()) { - return false; - } - - return _wildcard_match(p_wildcard.c_str(), c_str(), true); -} - -bool String::matchn(const String &p_wildcard) const { - if (!p_wildcard.length() || !length()) { - return false; - } - return _wildcard_match(p_wildcard.c_str(), c_str(), false); -} - -String String::format(const Variant &values, String placeholder) const { - String new_string = String(this->ptr()); - - if (values.get_type() == Variant::ARRAY) { - Array values_arr = values; - - for (int i = 0; i < values_arr.size(); i++) { - String i_as_str = String::num_int64(i); - - if (values_arr[i].get_type() == Variant::ARRAY) { //Array in Array structure [["name","RobotGuy"],[0,"pandemonium"],["strength",9000.91]] - Array value_arr = values_arr[i]; - - if (value_arr.size() == 2) { - Variant v_key = value_arr[0]; - String key = v_key; - - Variant v_val = value_arr[1]; - String val = v_val; - - new_string = new_string.replace(placeholder.replace("_", key), val); - } else { - ERR_PRINT(String("STRING.format Inner Array size != 2 ").ascii().get_data()); - } - } else { //Array structure ["RobotGuy","Logis","rookie"] - Variant v_val = values_arr[i]; - String val = v_val; - - if (placeholder.find("_") > -1) { - new_string = new_string.replace(placeholder.replace("_", i_as_str), val); - } else { - new_string = new_string.replace_first(placeholder, val); - } - } - } - } else if (values.get_type() == Variant::DICTIONARY) { - Dictionary d = values; - List keys; - d.get_key_list(&keys); - - for (List::Element *E = keys.front(); E; E = E->next()) { - String key = E->get(); - String val = d[E->get()]; - - new_string = new_string.replace(placeholder.replace("_", key), val); - } - } else { - ERR_PRINT(String("Invalid type: use Array or Dictionary.").ascii().get_data()); - } - - return new_string; -} - -String String::replace(const String &p_key, const String &p_with) const { - String new_string; - int search_from = 0; - int result = 0; - - while ((result = find(p_key, search_from)) >= 0) { - new_string += substr(search_from, result - search_from); - new_string += p_with; - search_from = result + p_key.length(); - } - - if (search_from == 0) { - return *this; - } - - new_string += substr(search_from, length() - search_from); - - return new_string; -} - -String String::replace(const char *p_key, const char *p_with) const { - String new_string; - int search_from = 0; - int result = 0; - - while ((result = find(p_key, search_from)) >= 0) { - new_string += substr(search_from, result - search_from); - new_string += p_with; - int k = 0; - while (p_key[k] != '\0') { - k++; - } - search_from = result + k; - } - - if (search_from == 0) { - return *this; - } - - new_string += substr(search_from, length() - search_from); - - return new_string; -} - -String String::replace_first(const String &p_key, const String &p_with) const { - int pos = find(p_key); - if (pos >= 0) { - return substr(0, pos) + p_with + substr(pos + p_key.length(), length()); - } - - return *this; -} -String String::replacen(const String &p_key, const String &p_with) const { - String new_string; - int search_from = 0; - int result = 0; - - while ((result = findn(p_key, search_from)) >= 0) { - new_string += substr(search_from, result - search_from); - new_string += p_with; - search_from = result + p_key.length(); - } - - if (search_from == 0) { - return *this; - } - - new_string += substr(search_from, length() - search_from); - return new_string; -} - -String String::newline_to_br() const { - String r = replace("\r\n", "
"); - return r.replace("\n", "
"); -} - -String String::repeat(int p_count) const { - ERR_FAIL_COND_V_MSG(p_count < 0, "", "Parameter count should be a positive number."); - - String new_string; - const CharType *src = this->c_str(); - - new_string.resize(length() * p_count + 1); - new_string[length() * p_count] = 0; - - for (int i = 0; i < p_count; i++) { - for (int j = 0; j < length(); j++) { - new_string[i * length() + j] = src[j]; - } - } - - return new_string; -} - -String String::left(int p_pos) const { - if (p_pos <= 0) { - return ""; - } - - if (p_pos >= length()) { - return *this; - } - - return substr(0, p_pos); -} - -String String::right(int p_pos) const { - if (p_pos >= length()) { - return ""; - } - - if (p_pos <= 0) { - return *this; - } - - return substr(p_pos, (length() - p_pos)); -} - -CharType String::ord_at(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, length(), 0); - return operator[](p_idx); -} - -String String::indent(const String &p_prefix) const { - String new_string; - int line_start = 0; - - for (int i = 0; i < length(); i++) { - const char32_t c = operator[](i); - if (c == '\n') { - if (i == line_start) { - new_string += c; // Leave empty lines empty. - } else { - new_string += p_prefix + substr(line_start, i - line_start + 1); - } - line_start = i + 1; - } - } - if (line_start != length()) { - new_string += p_prefix + substr(line_start); - } - return new_string; -} - -String String::dedent() const { - String new_string; - String indent; - bool has_indent = false; - bool has_text = false; - int line_start = 0; - int indent_stop = -1; - - for (int i = 0; i < length(); i++) { - CharType c = operator[](i); - if (c == '\n') { - if (has_text) { - new_string += substr(indent_stop, i - indent_stop); - } - new_string += "\n"; - has_text = false; - line_start = i + 1; - indent_stop = -1; - } else if (!has_text) { - if (c > 32) { - has_text = true; - if (!has_indent) { - has_indent = true; - indent = substr(line_start, i - line_start); - indent_stop = i; - } - } - if (has_indent && indent_stop < 0) { - int j = i - line_start; - if (j >= indent.length() || c != indent[j]) { - indent_stop = i; - } - } - } - } - - if (has_text) { - new_string += substr(indent_stop, length() - indent_stop); - } - - return new_string; -} - -String String::strip_edges(bool left, bool right) const { - int len = length(); - int beg = 0, end = len; - - if (left) { - for (int i = 0; i < len; i++) { - if (operator[](i) <= 32) { - beg++; - } else { - break; - } - } - } - - if (right) { - for (int i = (int)(len - 1); i >= 0; i--) { - if (operator[](i) <= 32) { - end--; - } else { - break; - } - } - } - - if (beg == 0 && end == len) { - return *this; - } - - return substr(beg, end - beg); -} - -String String::strip_escapes() const { - String new_string; - for (int i = 0; i < length(); i++) { - // Escape characters on first page of the ASCII table, before 32 (Space). - if (operator[](i) < 32) { - continue; - } - new_string += operator[](i); - } - - return new_string; -} - -String String::lstrip(const String &p_chars) const { - int len = length(); - int beg; - - for (beg = 0; beg < len; beg++) { - if (p_chars.find_char(get(beg)) == -1) { - break; - } - } - - if (beg == 0) { - return *this; - } - - return substr(beg, len - beg); -} - -String String::rstrip(const String &p_chars) const { - int len = length(); - int end; - - for (end = len - 1; end >= 0; end--) { - if (p_chars.find_char(get(end)) == -1) { - break; - } - } - - if (end == len - 1) { - return *this; - } - - return substr(0, end + 1); -} - -bool String::is_network_share_path() const { - return begins_with("//") || begins_with("\\\\"); -} - -String String::simplify_path() const { - String s = *this; - String drive; - if (s.begins_with("local://")) { - drive = "local://"; - s = s.substr(8, s.length()); - } else if (s.begins_with("res://")) { - drive = "res://"; - s = s.substr(6, s.length()); - } else if (s.begins_with("user://")) { - drive = "user://"; - s = s.substr(7, s.length()); - } else if (is_network_share_path()) { - drive = s.substr(0, 2); - s = s.substr(2, s.length() - 2); - } else if (s.begins_with("/") || s.begins_with("\\")) { - drive = s.substr(0, 1); - s = s.substr(1, s.length() - 1); - } else { - int p = s.find(":/"); - if (p == -1) { - p = s.find(":\\"); - } - if (p != -1 && p < s.find("/")) { - drive = s.substr(0, p + 2); - s = s.substr(p + 2, s.length()); - } - } - - s = s.replace("\\", "/"); - while (true) { // in case of using 2 or more slash - String compare = s.replace("//", "/"); - if (s == compare) { - break; - } else { - s = compare; - } - } - Vector dirs = s.split("/", false); - - for (int i = 0; i < dirs.size(); i++) { - String d = dirs[i]; - if (d == ".") { - dirs.remove(i); - i--; - } else if (d == "..") { - if (i == 0) { - dirs.remove(i); - i--; - } else { - dirs.remove(i); - dirs.remove(i - 1); - i -= 2; - } - } - } - - s = ""; - - for (int i = 0; i < dirs.size(); i++) { - if (i > 0) { - s += "/"; - } - s += dirs[i]; - } - - return drive + s; -} - -String String::append_path(const char *path) const { - if (path[0] == '\0') { - return *this; - } - - String ret = *this; - int size = length(); - - if (size == 0) { - ret += path; - return ret; - } - - int sindex = 0; - char ch = path[sindex]; - while (ch == '/' || ch == '\\') { - if (ch == '\0') { - return ret; - } - - ch = path[++sindex]; - } - - // /////folder - // ^ (sindex) - - if (ret.ends_with("/") || ret.ends_with("\\")) { - ret += &path[sindex]; - } else { - if (sindex > 0) { - ret += '/'; - ret += &path[sindex - 1]; - } else { - ret += '/'; - ret += &path[sindex]; - } - } - - return ret; -} - -String String::append_path(const String &path) const { - if (path.length() == 0) { - return *this; - } - - int size = length(); - - if (size == 0) { - return path; - } - - int sindex = 0; - int ts = path.size() - 1; - char ch = path[sindex]; - while (ch == '/' || ch == '\\') { - if (sindex == ts) { - return *this; - } - - ch = path[++sindex]; - } - - String ret = *this; - - // /////folder - // ^ (sindex) - - if (ret.ends_with("/") || ret.ends_with("\\")) { - ret += &path[sindex]; - } else { - if (sindex > 0) { - ret += '/'; - ret += &path[sindex - 1]; - } else { - ret += '/'; - ret += &path[sindex]; - } - } - - return ret; -} - -String String::path_clean_end_slash() const { - // _size > 1, so if root is given ("/"), it will not be removed - - String ret = *this; - - while (ret.length() > 1 && (ret.ends_with("/") || ret.ends_with("\\"))) { - ret.resize(ret.length()); - } - - return ret; -} -String String::path_ensure_end_slash() const { - // Don't add if empty string, as it would make it root on linux, which can easily become a serious bug - - String ret = *this; - - if (ret.length() == 0) { - return ret; - } - - if (!(ret.ends_with("/") || ret.ends_with("\\"))) { - ret += "/"; - } - - return ret; -} - -String String::path_get_prev_dir() const { - int size = length(); - - if (size == 0) { - return "/"; - } - - int seind = size - 1; - while (seind > 0 && (operator[](seind) == '/' || operator[](seind) == '\\')) { - --seind; - } - - if (seind == 0) { - // ///////// - // or - // a/////// - // no prev dir - - return "/"; - } - - // fol/fol2/fol3// - // ^ (seind) - - while (seind > 0 && (operator[](seind) != '/' && operator[](seind) != '\\')) { - --seind; - } - - // fol/fol2/fol3// - // ^ (seind) - - //--seind; - - if (seind <= 0) { - return "/"; - } - - return substr_index(0, seind); -} - -static int _humanize_digits(int p_num) { - if (p_num < 100) { - return 2; - } else if (p_num < 1024) { - return 1; - } else { - return 0; - } -} - -String String::humanize_size(uint64_t p_size) { - uint64_t _div = 1; - Vector prefixes; - prefixes.push_back(RTR("B")); - prefixes.push_back(RTR("KiB")); - prefixes.push_back(RTR("MiB")); - prefixes.push_back(RTR("GiB")); - prefixes.push_back(RTR("TiB")); - prefixes.push_back(RTR("PiB")); - prefixes.push_back(RTR("EiB")); - - int prefix_idx = 0; - - while (prefix_idx < prefixes.size() - 1 && p_size > (_div * 1024)) { - _div *= 1024; - prefix_idx++; - } - - const int digits = prefix_idx > 0 ? _humanize_digits(p_size / _div) : 0; - const double divisor = prefix_idx > 0 ? _div : 1; - - return String::num(p_size / divisor).pad_decimals(digits) + " " + prefixes[prefix_idx]; -} -bool String::is_abs_path() const { - if (length() > 1) { - return (operator[](0) == '/' || operator[](0) == '\\' || find(":/") != -1 || find(":\\") != -1); - } else if ((length()) == 1) { - return (operator[](0) == '/' || operator[](0) == '\\'); - } else { - return false; - } -} - -bool String::is_valid_identifier() const { - int len = length(); - - if (len == 0) { - return false; - } - - const wchar_t *str = &operator[](0); - - for (int i = 0; i < len; i++) { - if (i == 0) { - if (str[0] >= '0' && str[0] <= '9') { - return false; // no start with number plz - } - } - - bool valid_char = (str[i] >= '0' && str[i] <= '9') || (str[i] >= 'a' && str[i] <= 'z') || (str[i] >= 'A' && str[i] <= 'Z') || str[i] == '_'; - - if (!valid_char) { - return false; - } - } - - return true; -} - -//kind of poor should be rewritten properly - -String String::word_wrap(int p_chars_per_line) const { - int from = 0; - int last_space = 0; - String ret; - for (int i = 0; i < length(); i++) { - if (i - from >= p_chars_per_line) { - if (last_space == -1) { - ret += substr(from, i - from + 1) + "\n"; - } else { - ret += substr(from, last_space - from) + "\n"; - i = last_space; //rewind - } - from = i + 1; - last_space = -1; - } else if (operator[](i) == ' ' || operator[](i) == '\t') { - last_space = i; - } else if (operator[](i) == '\n') { - ret += substr(from, i - from) + "\n"; - from = i + 1; - last_space = -1; - } - } - - if (from < length()) { - ret += substr(from, length()); - } - - return ret; -} - -String String::http_escape() const { - const CharString temp = utf8(); - String res; - for (int i = 0; i < temp.length(); ++i) { - uint8_t ord = temp[i]; - if (ord == '.' || ord == '-' || ord == '_' || ord == '~' || - (ord >= 'a' && ord <= 'z') || - (ord >= 'A' && ord <= 'Z') || - (ord >= '0' && ord <= '9')) { - res += ord; - } else { - char p[4] = { '%', 0, 0, 0 }; - static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - - p[1] = hex[ord >> 4]; - p[2] = hex[ord & 0xF]; - res += p; - } - } - return res; -} - -String String::http_unescape() const { - String res; - for (int i = 0; i < length(); ++i) { - if (ord_at(i) == '%' && i + 2 < length()) { - CharType ord1 = ord_at(i + 1); - if ((ord1 >= '0' && ord1 <= '9') || (ord1 >= 'A' && ord1 <= 'Z')) { - CharType ord2 = ord_at(i + 2); - if ((ord2 >= '0' && ord2 <= '9') || (ord2 >= 'A' && ord2 <= 'Z')) { - char bytes[3] = { (char)ord1, (char)ord2, 0 }; - res += (char)strtol(bytes, nullptr, 16); - i += 2; - } - } else { - res += ord_at(i); - } - } else { - res += ord_at(i); - } - } - return String::utf8(res.ascii()); -} - -String String::uri_encode() const { - const CharString temp = utf8(); - String res; - for (int i = 0; i < temp.length(); ++i) { - uint8_t ord = temp[i]; - if (ord == '.' || ord == '-' || ord == '~' || is_ascii_identifier_char(ord)) { - res += ord; - } else { - char p[4] = { '%', 0, 0, 0 }; - static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - p[1] = hex[ord >> 4]; - p[2] = hex[ord & 0xF]; - res += p; - } - } - return res; -} - -String String::uri_decode() const { - CharString src = utf8(); - CharString res; - for (int i = 0; i < src.length(); ++i) { - if (src[i] == '%' && i + 2 < src.length()) { - char ord1 = src[i + 1]; - if (is_digit(ord1) || is_ascii_upper_case(ord1)) { - char ord2 = src[i + 2]; - if (is_digit(ord2) || is_ascii_upper_case(ord2)) { - char bytes[3] = { (char)ord1, (char)ord2, 0 }; - res += (char)strtol(bytes, nullptr, 16); - i += 2; - } - } else { - res += src[i]; - } - } else if (src[i] == '+') { - res += ' '; - } else { - res += src[i]; - } - } - return String::utf8(res); -} - -String String::c_unescape() const { - String escaped = *this; - escaped = escaped.replace("\\a", "\a"); - escaped = escaped.replace("\\b", "\b"); - escaped = escaped.replace("\\f", "\f"); - escaped = escaped.replace("\\n", "\n"); - escaped = escaped.replace("\\r", "\r"); - escaped = escaped.replace("\\t", "\t"); - escaped = escaped.replace("\\v", "\v"); - escaped = escaped.replace("\\'", "\'"); - escaped = escaped.replace("\\\"", "\""); - escaped = escaped.replace("\\?", "\?"); - escaped = escaped.replace("\\\\", "\\"); - - return escaped; -} - -String String::c_escape() const { - String escaped = *this; - escaped = escaped.replace("\\", "\\\\"); - escaped = escaped.replace("\a", "\\a"); - escaped = escaped.replace("\b", "\\b"); - escaped = escaped.replace("\f", "\\f"); - escaped = escaped.replace("\n", "\\n"); - escaped = escaped.replace("\r", "\\r"); - escaped = escaped.replace("\t", "\\t"); - escaped = escaped.replace("\v", "\\v"); - escaped = escaped.replace("\'", "\\'"); - escaped = escaped.replace("\?", "\\?"); - escaped = escaped.replace("\"", "\\\""); - - return escaped; -} - -String String::c_escape_multiline() const { - String escaped = *this; - escaped = escaped.replace("\\", "\\\\"); - escaped = escaped.replace("\"", "\\\""); - - return escaped; -} - -String String::json_escape() const { - String escaped = *this; - escaped = escaped.replace("\\", "\\\\"); - escaped = escaped.replace("\b", "\\b"); - escaped = escaped.replace("\f", "\\f"); - escaped = escaped.replace("\n", "\\n"); - escaped = escaped.replace("\r", "\\r"); - escaped = escaped.replace("\t", "\\t"); - escaped = escaped.replace("\v", "\\v"); - escaped = escaped.replace("\"", "\\\""); - - return escaped; -} - -String String::xml_escape(bool p_escape_quotes) const { - String str = *this; - str = str.replace("&", "&"); - str = str.replace("<", "<"); - str = str.replace(">", ">"); - if (p_escape_quotes) { - str = str.replace("'", "'"); - str = str.replace("\"", """); - } - /* - for (int i=1;i<32;i++) { - - char chr[2]={i,0}; - str=str.replace(chr,"&#"+String::num(i)+";"); - }*/ - return str; -} - -static _FORCE_INLINE_ int _xml_unescape(const CharType *p_src, int p_src_len, CharType *p_dst) { - int len = 0; - while (p_src_len) { - if (*p_src == '&') { - int eat = 0; - - if (p_src_len >= 4 && p_src[1] == '#') { - CharType c = 0; - bool overflow = false; - if (p_src[2] == 'x') { - // Hex entity &#x; - for (int i = 3; i < p_src_len; i++) { - eat = i + 1; - CharType ct = p_src[i]; - if (ct == ';') { - break; - } else if (ct >= '0' && ct <= '9') { - ct = ct - '0'; - } else if (ct >= 'a' && ct <= 'f') { - ct = (ct - 'a') + 10; - } else if (ct >= 'A' && ct <= 'F') { - ct = (ct - 'A') + 10; - } else { - break; - } - if (c > (WCHAR_MAX >> 4)) { - overflow = true; - break; - } - c <<= 4; - c |= ct; - } - } else { - // Decimal entity &#; - for (int i = 2; i < p_src_len; i++) { - eat = i + 1; - CharType ct = p_src[i]; - if (ct == ';' || ct < '0' || ct > '9') { - break; - } - } - if (p_src[eat - 1] == ';') { - int64_t val = String::to_int(p_src + 2, eat - 3); - if (val > 0 && val <= WCHAR_MAX) { - c = (CharType)val; - } else { - overflow = true; - } - } - } - - // Value must be non-zero, in the range of char32_t, - // actually end with ';'. If invalid, leave the entity as-is - if (c == '\0' || overflow || p_src[eat - 1] != ';') { - eat = 1; - c = *p_src; - } - if (p_dst) { - *p_dst = c; - } - } else if (p_src_len >= 4 && p_src[1] == 'g' && p_src[2] == 't' && p_src[3] == ';') { - if (p_dst) { - *p_dst = '>'; - } - eat = 4; - } else if (p_src_len >= 4 && p_src[1] == 'l' && p_src[2] == 't' && p_src[3] == ';') { - if (p_dst) { - *p_dst = '<'; - } - eat = 4; - } else if (p_src_len >= 5 && p_src[1] == 'a' && p_src[2] == 'm' && p_src[3] == 'p' && p_src[4] == ';') { - if (p_dst) { - *p_dst = '&'; - } - eat = 5; - } else if (p_src_len >= 6 && p_src[1] == 'q' && p_src[2] == 'u' && p_src[3] == 'o' && p_src[4] == 't' && p_src[5] == ';') { - if (p_dst) { - *p_dst = '"'; - } - eat = 6; - } else if (p_src_len >= 6 && p_src[1] == 'a' && p_src[2] == 'p' && p_src[3] == 'o' && p_src[4] == 's' && p_src[5] == ';') { - if (p_dst) { - *p_dst = '\''; - } - eat = 6; - } else { - if (p_dst) { - *p_dst = *p_src; - } - eat = 1; - } - - if (p_dst) { - p_dst++; - } - - len++; - p_src += eat; - p_src_len -= eat; - } else { - if (p_dst) { - *p_dst = *p_src; - p_dst++; - } - len++; - p_src++; - p_src_len--; - } - } - - return len; -} - -String String::xml_unescape() const { - String str; - int l = length(); - int len = _xml_unescape(c_str(), l, nullptr); - if (len == 0) { - return String(); - } - str.resize(len + 1); - _xml_unescape(c_str(), l, str.ptrw()); - str[len] = 0; - return str; -} - -String String::pad_decimals(int p_digits) const { - String s = *this; - int c = s.find("."); - - if (c == -1) { - if (p_digits <= 0) { - return s; - } - s += "."; - c = s.length() - 1; - } else { - if (p_digits <= 0) { - return s.substr(0, c); - } - } - - if (s.length() - (c + 1) > p_digits) { - s = s.substr(0, c + p_digits + 1); - } else { - while (s.length() - (c + 1) < p_digits) { - s += "0"; - } - } - return s; -} - -String String::pad_zeros(int p_digits) const { - String s = *this; - int end = s.find("."); - - if (end == -1) { - end = s.length(); - } - - if (end == 0) { - return s; - } - - int begin = 0; - - while (begin < end && (s[begin] < '0' || s[begin] > '9')) { - begin++; - } - - if (begin >= end) { - return s; - } - - while (end - begin < p_digits) { - s = s.insert(begin, "0"); - end++; - } - - return s; -} - -String String::trim_prefix(const String &p_prefix) const { - String s = *this; - if (s.begins_with(p_prefix)) { - return s.substr(p_prefix.length(), s.length() - p_prefix.length()); - } - return s; -} - -String String::trim_suffix(const String &p_suffix) const { - String s = *this; - if (s.ends_with(p_suffix)) { - return s.substr(0, s.length() - p_suffix.length()); - } - return s; -} - -bool String::is_valid_integer() const { - int len = length(); - - if (len == 0) { - return false; - } - - int from = 0; - if (len != 1 && (operator[](0) == '+' || operator[](0) == '-')) { - from++; - } - - for (int i = from; i < len; i++) { - if (operator[](i) < '0' || operator[](i) > '9') { - return false; // no start with number plz - } - } - - return true; -} - -bool String::is_valid_hex_number(bool p_with_prefix) const { - int len = length(); - - if (len == 0) { - return false; - } - - int from = 0; - if (len != 1 && (operator[](0) == '+' || operator[](0) == '-')) { - from++; - } - - if (p_with_prefix) { - if (len < 3) { - return false; - } - if (operator[](from) != '0' || operator[](from + 1) != 'x') { - return false; - } - from += 2; - } - - for (int i = from; i < len; i++) { - CharType c = operator[](i); - if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { - continue; - } - return false; - } - - return true; -}; - -bool String::is_valid_float() const { - int len = length(); - - if (len == 0) { - return false; - } - - int from = 0; - if (operator[](0) == '+' || operator[](0) == '-') { - from++; - } - - bool exponent_found = false; - bool period_found = false; - bool sign_found = false; - bool exponent_values_found = false; - bool numbers_found = false; - - for (int i = from; i < len; i++) { - if (operator[](i) >= '0' && operator[](i) <= '9') { - if (exponent_found) { - exponent_values_found = true; - } else { - numbers_found = true; - } - } else if (numbers_found && !exponent_found && operator[](i) == 'e') { - exponent_found = true; - } else if (!period_found && !exponent_found && operator[](i) == '.') { - period_found = true; - } else if ((operator[](i) == '-' || operator[](i) == '+') && exponent_found && !exponent_values_found && !sign_found) { - sign_found = true; - } else { - return false; // no start with number plz - } - } - - return numbers_found; -} - -bool String::is_valid_bool() const { - int size = length(); - - if (size == 1) { - CharType c = ptr()[0]; - - if (c == '0') { - return true; - } else if (c == '1') { - return true; - } - - return false; - } else if (size == 4) { - String l = to_lower(); - const CharType *p = l.ptr(); - - if (p[0] == 't' && p[1] == 'r' && p[2] == 'u' && p[3] == 'e') { - return true; - } else { - return false; - } - } else if (size == 5) { - String l = to_lower(); - const CharType *p = l.ptr(); - - if (p[0] == 'f' && p[1] == 'a' && p[2] == 'l' && p[3] == 's' && p[3] == 'e') { - return true; - } else { - return false; - } - } - - return false; -} - -bool String::is_valid_unsigned_integer() const { - int len = length(); - - if (len == 0) { - return false; - } - - int from = 0; - if (len != 1 && (operator[](0) == '+')) { - from++; - } - - for (int i = from; i < len; i++) { - if (operator[](i) < '0' || operator[](i) > '9') { - return false; // no start with number plz - } - } - - return true; -} - -String String::path_to_file(const String &p_path) const { - // Don't get base dir for src, this is expected to be a dir already. - String src = this->replace("\\", "/"); - String dst = p_path.replace("\\", "/").get_base_dir(); - String rel = src.path_to(dst); - if (rel == dst) { // failed - return p_path; - } else { - return rel + p_path.get_file(); - } +bool operator==(const wchar_t *p_chr, const String &p_str) { +#ifdef WINDOWS_ENABLED + // wchar_t is 16-bit + return p_str == String::utf16((const char16_t *)p_chr); +#else + // wchar_t is 32-bi + return p_str == String((const CharType *)p_chr); +#endif } -String String::path_to(const String &p_path) const { - String src = this->replace("\\", "/"); - String dst = p_path.replace("\\", "/"); - if (!src.ends_with("/")) { - src += "/"; - } - if (!dst.ends_with("/")) { - dst += "/"; - } - - String base; - - if (src.begins_with("res://") && dst.begins_with("res://")) { - base = "res:/"; - src = src.replace("res://", "/"); - dst = dst.replace("res://", "/"); - - } else if (src.begins_with("user://") && dst.begins_with("user://")) { - base = "user:/"; - src = src.replace("user://", "/"); - dst = dst.replace("user://", "/"); - - } else if (src.begins_with("/") && dst.begins_with("/")) { - //nothing - } else { - //dos style - String src_begin = src.get_slicec('/', 0); - String dst_begin = dst.get_slicec('/', 0); - - if (src_begin != dst_begin) { - return p_path; //impossible to do this - } - - base = src_begin; - src = src.substr(src_begin.length(), src.length()); - dst = dst.substr(dst_begin.length(), dst.length()); - } - - //remove leading and trailing slash and split - Vector src_dirs = src.substr(1, src.length() - 2).split("/"); - Vector dst_dirs = dst.substr(1, dst.length() - 2).split("/"); - - //find common parent - int common_parent = 0; - - while (true) { - if (src_dirs.size() == common_parent) { - break; - } - if (dst_dirs.size() == common_parent) { - break; - } - if (src_dirs[common_parent] != dst_dirs[common_parent]) { - break; - } - common_parent++; - } - - common_parent--; - - String dir; - - for (int i = src_dirs.size() - 1; i > common_parent; i--) { - dir += "../"; - } - - for (int i = common_parent + 1; i < dst_dirs.size(); i++) { - dir += dst_dirs[i] + "/"; - } - - if (dir.length() == 0) { - dir = "./"; - } - return dir; +bool operator!=(const char *p_chr, const String &p_str) { + return !(p_str == p_chr); } -bool String::is_valid_html_color() const { - return Color::html_is_valid(*this); +bool operator!=(const wchar_t *p_chr, const String &p_str) { +#ifdef WINDOWS_ENABLED + // wchar_t is 16-bit + return !(p_str == String::utf16((const char16_t *)p_chr)); +#else + // wchar_t is 32-bi + return !(p_str == String((const CharType *)p_chr)); +#endif } -bool String::is_valid_filename() const { - String stripped = strip_edges(); - if (*this != stripped) { - return false; - } - - if (stripped == String()) { - return false; - } - - return !(find(":") != -1 || find("/") != -1 || find("\\") != -1 || find("?") != -1 || find("*") != -1 || find("\"") != -1 || find("|") != -1 || find("%") != -1 || find("<") != -1 || find(">") != -1); +String operator+(const char *p_chr, const String &p_str) { + String tmp = p_chr; + tmp += p_str; + return tmp; } -bool String::is_valid_ip_address() const { - if (find(":") >= 0) { - Vector ip = split(":"); - for (int i = 0; i < ip.size(); i++) { - String n = ip[i]; - if (n.empty()) { - continue; - } - if (n.is_valid_hex_number(false)) { - int nint = n.hex_to_int(false); - if (nint < 0 || nint > 0xffff) { - return false; - } - continue; - }; - if (!n.is_valid_ip_address()) { - return false; - } - }; - - } else { - Vector ip = split("."); - if (ip.size() != 4) { - return false; - } - for (int i = 0; i < ip.size(); i++) { - String n = ip[i]; - if (!n.is_valid_integer()) { - return false; - } - int val = n.to_int(); - if (val < 0 || val > 255) { - return false; - } - } - }; - - return true; +String operator+(const wchar_t *p_chr, const String &p_str) { +#ifdef WINDOWS_ENABLED + // wchar_t is 16-bit + String tmp = String::utf16((const char16_t *)p_chr); +#else + // wchar_t is 32-bit + String tmp = (const CharType *)p_chr; +#endif + tmp += p_str; + return tmp; } -bool String::is_resource_file() const { - return begins_with("res://") && find("::") == -1; -} - -bool String::is_rel_path() const { - return !is_abs_path(); -} - -String String::get_base_dir() const { - int end = 0; - - // url scheme style base - int basepos = find("://"); - if (basepos != -1) { - end = basepos + 3; - } - - // windows top level directory base - if (end == 0) { - basepos = find(":/"); - if (basepos == -1) { - basepos = find(":\\"); - } - if (basepos != -1) { - end = basepos + 2; - } - } - - // Windows UNC network share path. - if (end == 0) { - if (is_network_share_path()) { - basepos = find("/", 2); - if (basepos == -1) { - basepos = find("\\", 2); - } - int servpos = find("/", basepos + 1); - if (servpos == -1) { - servpos = find("\\", basepos + 1); - } - if (servpos != -1) { - end = servpos + 1; - } - } - } - - // unix root directory base - if (end == 0) { - if (begins_with("/")) { - end = 1; - } - } - - String rs; - String base; - if (end != 0) { - rs = substr(end, length()); - base = substr(0, end); - } else { - rs = *this; - } - - int sep = MAX(rs.rfind("/"), rs.rfind("\\")); - if (sep == -1) { - return base; - } - - return base + rs.substr(0, sep); -} - -String String::get_file() const { - int sep = MAX(rfind("/"), rfind("\\")); - if (sep == -1) { - return *this; - } - - return substr(sep + 1, length()); -} - -String String::get_extension() const { - int pos = rfind("."); - if (pos < 0 || pos < MAX(rfind("/"), rfind("\\"))) { - return ""; - } - - return substr(pos + 1, length()); -} - -String String::plus_file(const String &p_file) const { - if (empty()) { - return p_file; - } - if (operator[](length() - 1) == '/' || (p_file.size() > 0 && p_file.operator[](0) == '/')) { - return *this + p_file; - } - return *this + "/" + p_file; -} - -String String::percent_encode() const { - CharString cs = utf8(); - String encoded; - for (int i = 0; i < cs.length(); i++) { - uint8_t c = cs[i]; - if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '~' || c == '.') { - char p[2] = { (char)c, 0 }; - encoded += p; - } else { - char p[4] = { '%', 0, 0, 0 }; - static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - - p[1] = hex[c >> 4]; - p[2] = hex[c & 0xF]; - encoded += p; - } - } - - return encoded; -} -String String::percent_decode() const { - CharString pe; - - CharString cs = utf8(); - for (int i = 0; i < cs.length(); i++) { - uint8_t c = cs[i]; - if (c == '%' && i < length() - 2) { - uint8_t a = LOWERCASE(cs[i + 1]); - uint8_t b = LOWERCASE(cs[i + 2]); - - if (a >= '0' && a <= '9') { - c = (a - '0') << 4; - } else if (a >= 'a' && a <= 'f') { - c = (a - 'a' + 10) << 4; - } else { - continue; - } - - uint8_t d = 0; - - if (b >= '0' && b <= '9') { - d = (b - '0'); - } else if (b >= 'a' && b <= 'f') { - d = (b - 'a' + 10); - } else { - continue; - } - c += d; - i += 2; - } - pe += c; - } - - return String::utf8(pe.ptr()); -} - -String String::property_name_encode() const { - // Escape and quote strings with extended ASCII or further Unicode characters - // as well as '"', '=' or ' ' (32) - const CharType *cstr = c_str(); - for (int i = 0; cstr[i]; i++) { - if (cstr[i] == '=' || cstr[i] == '"' || cstr[i] == ';' || cstr[i] == '[' || cstr[i] == ']' || cstr[i] < 33 || cstr[i] > 126) { - return "\"" + c_escape_multiline() + "\""; - } - } - // Keep as is - return *this; -} - -// Changes made to the set of invalid characters must also be reflected in the String documentation. -const String String::invalid_node_name_characters = ". : @ / \" %"; - -String String::validate_node_name() const { - Vector chars = String::invalid_node_name_characters.split(" "); - String name = this->replace(chars[0], ""); - for (int i = 1; i < chars.size(); i++) { - name = name.replace(chars[i], ""); - } - return name; -} - -String String::get_basename() const { - int pos = rfind("."); - if (pos < 0 || pos < MAX(rfind("/"), rfind("\\"))) { - return *this; - } - - return substr(0, pos); +String operator+(CharType p_chr, const String &p_str) { + return (String::chr(p_chr) + p_str); } String itos(int64_t p_val) { @@ -4596,325 +5585,6 @@ String rtoss(double p_val) { return String::num_scientific(p_val); } -// Right-pad with a character. -String String::rpad(int min_length, const String &character) const { - String s = *this; - int padding = min_length - s.length(); - if (padding > 0) { - for (int i = 0; i < padding; i++) { - s = s + character; - } - } - - return s; -} -// Left-pad with a character. -String String::lpad(int min_length, const String &character) const { - String s = *this; - int padding = min_length - s.length(); - if (padding > 0) { - for (int i = 0; i < padding; i++) { - s = character + s; - } - } - - return s; -} - -// sprintf is implemented in GDScript via: -// "fish %s pie" % "frog" -// "fish %s %d pie" % ["frog", 12] -// In case of an error, the string returned is the error description and "error" is true. -String String::sprintf(const Array &values, bool *error) const { - String formatted; - CharType *self = (CharType *)c_str(); - bool in_format = false; - int value_index = 0; - int min_chars = 0; - int min_decimals = 0; - bool in_decimals = false; - bool pad_with_zeros = false; - bool left_justified = false; - bool show_sign = false; - - *error = true; - - for (; *self; self++) { - const CharType c = *self; - - if (in_format) { // We have % - lets see what else we get. - switch (c) { - case '%': { // Replace %% with % - formatted += chr(c); - in_format = false; - break; - } - case 'd': // Integer (signed) - case 'o': // Octal - case 'x': // Hexadecimal (lowercase) - case 'X': { // Hexadecimal (uppercase) - if (value_index >= values.size()) { - return "not enough arguments for format string"; - } - - if (!values[value_index].is_num()) { - return "a number is required"; - } - - int64_t value = values[value_index]; - int base = 16; - bool capitalize = false; - switch (c) { - case 'd': - base = 10; - break; - case 'o': - base = 8; - break; - case 'x': - break; - case 'X': - base = 16; - capitalize = true; - break; - } - // Get basic number. - String str = String::num_int64(ABS(value), base, capitalize); - int number_len = str.length(); - - // Padding. - int pad_chars_count = (value < 0 || show_sign) ? min_chars - 1 : min_chars; - String pad_char = pad_with_zeros ? String("0") : String(" "); - if (left_justified) { - str = str.rpad(pad_chars_count, pad_char); - } else { - str = str.lpad(pad_chars_count, pad_char); - } - - // Sign. - if (show_sign || value < 0) { - String sign_char = value < 0 ? "-" : "+"; - if (left_justified) { - str = str.insert(0, sign_char); - } else { - str = str.insert(pad_with_zeros ? 0 : str.length() - number_len, sign_char); - } - } - - formatted += str; - ++value_index; - in_format = false; - - break; - } - case 'f': { // Float - if (value_index >= values.size()) { - return "not enough arguments for format string"; - } - - if (!values[value_index].is_num()) { - return "a number is required"; - } - - double value = values[value_index]; - bool is_negative = (value < 0); - String str = String::num(ABS(value), min_decimals); - - // Pad decimals out. - str = str.pad_decimals(min_decimals); - - int initial_len = str.length(); - - // Padding. Leave room for sign later if required. - int pad_chars_count = (is_negative || show_sign) ? min_chars - 1 : min_chars; - String pad_char = pad_with_zeros ? String("0") : String(" "); - if (left_justified) { - str = str.rpad(pad_chars_count, pad_char); - } else { - str = str.lpad(pad_chars_count, pad_char); - } - - // Add sign if needed. - if (show_sign || is_negative) { - String sign_char = is_negative ? "-" : "+"; - if (left_justified) { - str = str.insert(0, sign_char); - } else { - str = str.insert(pad_with_zeros ? 0 : str.length() - initial_len, sign_char); - } - } - - formatted += str; - ++value_index; - in_format = false; - break; - } - case 's': { // String - if (value_index >= values.size()) { - return "not enough arguments for format string"; - } - - String str = values[value_index]; - // Padding. - if (left_justified) { - str = str.rpad(min_chars); - } else { - str = str.lpad(min_chars); - } - - formatted += str; - ++value_index; - in_format = false; - break; - } - case 'c': { - if (value_index >= values.size()) { - return "not enough arguments for format string"; - } - - // Convert to character. - String str; - if (values[value_index].is_num()) { - int value = values[value_index]; - if (value < 0) { - return "unsigned byte integer is lower than maximum"; - } else if (value > 255) { - return "unsigned byte integer is greater than maximum"; - } - str = chr(values[value_index]); - } else if (values[value_index].get_type() == Variant::STRING) { - str = values[value_index]; - if (str.length() != 1) { - return "%c requires number or single-character string"; - } - } else { - return "%c requires number or single-character string"; - } - - // Padding. - if (left_justified) { - str = str.rpad(min_chars); - } else { - str = str.lpad(min_chars); - } - - formatted += str; - ++value_index; - in_format = false; - break; - } - case '-': { // Left justify - left_justified = true; - break; - } - case '+': { // Show + if positive. - show_sign = true; - break; - } - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': { - int n = c - '0'; - if (in_decimals) { - min_decimals *= 10; - min_decimals += n; - } else { - if (c == '0' && min_chars == 0) { - if (left_justified) { - WARN_PRINT("'0' flag ignored with '-' flag in string format"); - } else { - pad_with_zeros = true; - } - } else { - min_chars *= 10; - min_chars += n; - } - } - break; - } - case '.': { // Float separator. - if (in_decimals) { - return "too many decimal points in format"; - } - in_decimals = true; - min_decimals = 0; // We want to add the value manually. - break; - } - - case '*': { // Dynamic width, based on value. - if (value_index >= values.size()) { - return "not enough arguments for format string"; - } - - if (!values[value_index].is_num()) { - return "* wants number"; - } - - int size = values[value_index]; - - if (in_decimals) { - min_decimals = size; - } else { - min_chars = size; - } - - ++value_index; - break; - } - - default: { - return "unsupported format character"; - } - } - } else { // Not in format string. - switch (c) { - case '%': - in_format = true; - // Back to defaults: - min_chars = 0; - min_decimals = 6; - pad_with_zeros = false; - left_justified = false; - show_sign = false; - in_decimals = false; - break; - default: - formatted += chr(c); - } - } - } - - if (in_format) { - return "incomplete format"; - } - - if (value_index != values.size()) { - return "not all arguments converted during string formatting"; - } - - *error = false; - return formatted; -} - -String String::quote(String quotechar) const { - return quotechar + *this + quotechar; -} - -String String::unquote() const { - if (!is_quoted()) { - return *this; - } - - return substr(1, length() - 2); -} - #ifdef TOOLS_ENABLED String TTR(const String &p_text) { if (TranslationServer::get_singleton()) { diff --git a/core/ustring.h b/core/ustring.h index 13c297507..24d1ae3b7 100644 --- a/core/ustring.h +++ b/core/ustring.h @@ -36,8 +36,13 @@ #include "core/typedefs.h" #include "core/vector.h" +/*************************************************************************/ +/* CharProxy */ +/*************************************************************************/ + template class CharProxy { + friend class Char16String; friend class CharString; friend class String; @@ -75,6 +80,51 @@ public: } }; +/*************************************************************************/ +/* Char16String */ +/*************************************************************************/ + +class Char16String { + CowData _cowdata; + static const char16_t _null; + +public: + _FORCE_INLINE_ char16_t *ptrw() { return _cowdata.ptrw(); } + _FORCE_INLINE_ const char16_t *ptr() const { return _cowdata.ptr(); } + _FORCE_INLINE_ int size() const { return _cowdata.size(); } + Error resize(int p_size) { return _cowdata.resize(p_size); } + + _FORCE_INLINE_ char16_t get(int p_index) const { return _cowdata.get(p_index); } + _FORCE_INLINE_ void set(int p_index, const char16_t &p_elem) { _cowdata.set(p_index, p_elem); } + _FORCE_INLINE_ const char16_t &operator[](int p_index) const { + if (unlikely(p_index == _cowdata.size())) { + return _null; + } + + return _cowdata.get(p_index); + } + _FORCE_INLINE_ CharProxy operator[](int p_index) { return CharProxy(p_index, _cowdata); } + + _FORCE_INLINE_ Char16String() {} + _FORCE_INLINE_ Char16String(const Char16String &p_str) { _cowdata._ref(p_str._cowdata); } + _FORCE_INLINE_ void operator=(const Char16String &p_str) { _cowdata._ref(p_str._cowdata); } + _FORCE_INLINE_ Char16String(const char16_t *p_cstr) { copy_from(p_cstr); } + + void operator=(const char16_t *p_cstr); + bool operator<(const Char16String &p_right) const; + Char16String &operator+=(char16_t p_char); + int length() const { return size() ? size() - 1 : 0; } + const char16_t *get_data() const; + operator const char16_t *() const { return get_data(); }; + +protected: + void copy_from(const char16_t *p_cstr); +}; + +/*************************************************************************/ +/* CharString */ +/*************************************************************************/ + class CharString { CowData _cowdata; static const char _null; @@ -98,13 +148,10 @@ public: _FORCE_INLINE_ CharString() {} _FORCE_INLINE_ CharString(const CharString &p_str) { _cowdata._ref(p_str._cowdata); } - _FORCE_INLINE_ CharString operator=(const CharString &p_str) { - _cowdata._ref(p_str._cowdata); - return *this; - } + _FORCE_INLINE_ void operator=(const CharString &p_str) { _cowdata._ref(p_str._cowdata); } _FORCE_INLINE_ CharString(const char *p_cstr) { copy_from(p_cstr); } - CharString &operator=(const char *p_cstr); + void operator=(const char *p_cstr); bool operator<(const CharString &p_right) const; CharString &operator+=(char p_char); int length() const { return size() ? size() - 1 : 0; } @@ -115,7 +162,11 @@ protected: void copy_from(const char *p_cstr); }; -typedef wchar_t CharType; +/*************************************************************************/ +/* String */ +/*************************************************************************/ + +typedef char32_t CharType; struct StrRange { const CharType *c_str; @@ -128,19 +179,8 @@ struct StrRange { }; class String { - CowData _cowdata; - static const CharType _null; - - void copy_from(const char *p_cstr); - void copy_from(const CharType *p_cstr, const int p_clip_to = -1); - void copy_from(const CharType &p_char); - void copy_from_unchecked(const CharType *p_char, const int p_length); - bool _base_is_subsequence_of(const String &p_string, bool case_insensitive) const; - int _count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const; - public: enum { - npos = -1 ///(const String &p_str) const; + bool operator>=(const String &p_str) const; signed char casecmp_to(const String &p_str) const; signed char nocasecmp_to(const String &p_str) const; signed char naturalnocasecmp_to(const String &p_str) const; - const CharType *c_str() const; + const CharType *get_data() const; + /* standard size stuff */ _FORCE_INLINE_ int length() const { @@ -201,6 +253,11 @@ public: return s ? (s - 1) : 0; // length does not include zero } + bool is_valid_string() const; + + /* debug, error messages */ + void print_unicode_error(const String &p_message, bool p_critical = false) const; + /* complex helpers */ String substr(int p_from, int p_chars = -1) const; String substr_index(const int p_start_index, const int p_end_index) const; //end_index is not included @@ -256,7 +313,6 @@ public: String sprintf(const Array &values, bool *error) const; String quote(String quotechar = "\"") const; - String unquote() const; static String num(double p_num, int p_decimals = -1); @@ -264,9 +320,13 @@ public: static String num_real(double p_num); static String num_int64(int64_t p_num, int base = 10, bool capitalize_hex = false); static String num_uint64(uint64_t p_num, int base = 10, bool capitalize_hex = false); + static String chr(CharType p_char); + static String md5(const uint8_t *p_md5); + static String hex_encode_buffer(const uint8_t *p_buffer, int p_len); + static String bool_num(bool p_val); static String bool_str(bool p_val); @@ -275,21 +335,31 @@ public: double to_double() const; float to_float() const; - int hex_to_int(bool p_with_prefix = true) const; int to_int() const; bool to_bool() const; uint32_t to_uint() const; + int hex_to_int(bool p_with_prefix = true) const; int64_t hex_to_int64(bool p_with_prefix = true) const; int64_t bin_to_int64(bool p_with_prefix = true) const; int64_t to_int64() const; - static int to_int(const char *p_str, int p_len = -1); + + static int64_t to_int(const char *p_str, int p_len = -1); + static int64_t to_int(const wchar_t *p_str, int p_len = -1); + static int64_t to_int(const CharType *p_str, int p_len = -1, bool p_clamp = false); + + static double to_float(const char *p_str); + static double to_float(const wchar_t *p_str, const wchar_t **r_end = nullptr); + static double to_float(const CharType *p_str, const CharType **r_end = nullptr); + static double to_double(const char *p_str); + static double to_double(const wchar_t *p_str, const wchar_t **r_end = nullptr); static double to_double(const CharType *p_str, const CharType **r_end = nullptr); - static int64_t to_int(const CharType *p_str, int p_len = -1); + String capitalize() const; String camelcase_to_underscore(bool lowercase = true) const; + String get_with_code_lines() const; int get_slice_count(String p_splitter) const; String get_slice(String p_splitter, int p_slice) const; String get_slicec(CharType p_splitter, int p_slice) const; @@ -316,28 +386,40 @@ public: String right(int p_pos) const; String indent(const String &p_prefix) const; String dedent() const; + String strip_edges(bool left = true, bool right = true) const; String strip_escapes() const; String lstrip(const String &p_chars) const; String rstrip(const String &p_chars) const; + String get_extension() const; String get_basename() const; String plus_file(const String &p_file) const; + + CharType unicode_at(int p_idx) const; CharType ord_at(int p_idx) const; void erase(int p_pos, int p_chars); CharString ascii(bool p_allow_extended = false) const; CharString utf8() const; - bool parse_utf8(const char *p_utf8, int p_len = -1, bool p_skip_cr = false); //return true on error + Error parse_utf8(const char *p_utf8, int p_len = -1, bool p_skip_cr = false); //return true on error static String utf8(const char *p_utf8, int p_len = -1); - static uint32_t hash(const CharType *p_cstr, int p_len); /* hash the string */ - static uint32_t hash(const CharType *p_cstr); /* hash the string */ - static uint32_t hash(const char *p_cstr, int p_len); /* hash the string */ + Char16String utf16() const; + Error parse_utf16(const char16_t *p_utf16, int p_len = -1); + static String utf16(const char16_t *p_utf16, int p_len = -1); + static uint32_t hash(const char *p_cstr); /* hash the string */ + static uint32_t hash(const char *p_cstr, int p_len); /* hash the string */ + static uint32_t hash(const wchar_t *p_cstr); /* hash the string */ + static uint32_t hash(const wchar_t *p_cstr, int p_len); /* hash the string */ + static uint32_t hash(const CharType *p_cstr); /* hash the string */ + static uint32_t hash(const CharType *p_cstr, int p_len); /* hash the string */ + uint32_t hash() const; /* hash the string */ uint64_t hash64() const; /* hash the string */ + String md5_text() const; String sha1_text() const; String sha256_text() const; @@ -387,6 +469,7 @@ public: // node functions static const String invalid_node_name_characters; String validate_node_name() const; + String validate_identifier() const; //! bool is_valid_identifier() const; bool is_valid_integer() const; @@ -405,19 +488,50 @@ public: _FORCE_INLINE_ String() {} _FORCE_INLINE_ String(const String &p_str) { _cowdata._ref(p_str._cowdata); } - String operator=(const String &p_str) { + //!!! why void + void operator=(const String &p_str) { _cowdata._ref(p_str._cowdata); - return *this; } + Vector to_ascii_buffer() const; + Vector to_utf8_buffer() const; + Vector to_utf16_buffer() const; + Vector to_utf32_buffer() const; + String(const char *p_str); - String(const CharType *p_str, int p_clip_to_len = -1); + String(const wchar_t *p_str); + String(const CharType *p_str); + String(const char *p_str, int p_clip_to_len); + String(const wchar_t *p_str, int p_clip_to_len); + String(const CharType *p_str, int p_clip_to_len); String(const StrRange &p_range); + +private: + CowData _cowdata; + static const CharType _null; + + void copy_from(const char *p_cstr); + void copy_from(const char *p_cstr, const int p_clip_to); + void copy_from(const wchar_t *p_cstr); + void copy_from(const wchar_t *p_cstr, const int p_clip_to); + void copy_from(const CharType *p_cstr); + void copy_from(const CharType *p_cstr, const int p_clip_to); + + void copy_from(const CharType &p_char); + + void copy_from_unchecked(const CharType *p_char, const int p_length); + + bool _base_is_subsequence_of(const String &p_string, bool case_insensitive) const; + int _count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const; }; bool operator==(const char *p_chr, const String &p_str); +bool operator==(const wchar_t *p_chr, const String &p_str); +bool operator!=(const char *p_chr, const String &p_str); +bool operator!=(const wchar_t *p_chr, const String &p_str); String operator+(const char *p_chr, const String &p_str); +String operator+(const wchar_t *p_chr, const String &p_str); String operator+(CharType p_chr, const String &p_str); String itos(int64_t p_val); @@ -440,15 +554,18 @@ struct NaturalNoCaseComparator { template _FORCE_INLINE_ bool is_str_less(const L *l_ptr, const R *r_ptr) { while (true) { - if (*l_ptr == 0 && *r_ptr == 0) { + const CharType l = *l_ptr; + const CharType r = *r_ptr; + + if (l == 0 && r == 0) { return false; - } else if (*l_ptr == 0) { + } else if (l == 0) { return true; - } else if (*r_ptr == 0) { + } else if (r == 0) { return false; - } else if (*l_ptr < *r_ptr) { + } else if (l < r) { return true; - } else if (*l_ptr > *r_ptr) { + } else if (l > r) { return false; } @@ -480,7 +597,6 @@ String DTR(const String &); // Runtime translate for the public node API. String RTR(const String &); -bool is_symbol(CharType c); bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end); #endif // USTRING_H diff --git a/core/variant_call.cpp b/core/variant_call.cpp index 480f8e641..58e39aad7 100644 --- a/core/variant_call.cpp +++ b/core/variant_call.cpp @@ -418,6 +418,39 @@ struct _VariantCall { r_ret = retval; } + static void _call_String_to_utf16(Variant &r_ret, Variant &p_self, const Variant **p_args) { + String *s = reinterpret_cast(p_self._data._mem); + if (s->empty()) { + r_ret = PoolByteArray(); + return; + } + Char16String charstr = s->utf16(); + + PoolByteArray retval; + size_t len = charstr.length() * 2; + retval.resize(len); + PoolByteArray::Write w = retval.write(); + memcpy(w.ptr(), s->ptr(), len); + + r_ret = retval; + } + + static void _call_String_to_utf32(Variant &r_ret, Variant &p_self, const Variant **p_args) { + String *s = reinterpret_cast(p_self._data._mem); + if (s->empty()) { + r_ret = PoolByteArray(); + return; + } + + PoolByteArray retval; + size_t len = s->length() * 4; + retval.resize(len); + PoolByteArray::Write w = retval.write(); + memcpy(w.ptr(), s->ptr(), len); + + r_ret = retval; + } + VCALL_LOCALMEM1(Vector2, set_all); VCALL_LOCALMEM0R(Vector2, min_axis); VCALL_LOCALMEM0R(Vector2, max_axis); @@ -787,6 +820,26 @@ struct _VariantCall { r_ret = s; } + static void _call_PoolByteArray_get_string_from_utf16(Variant &r_ret, Variant &p_self, const Variant **p_args) { + PoolByteArray *ba = reinterpret_cast(p_self._data._mem); + String s; + if (ba->size() > 0) { + PoolByteArray::Read r = ba->read(); + s.parse_utf16((const char16_t *)r.ptr(), ba->size()/ 2); + } + r_ret = s; + } + + static void _call_PoolByteArray_get_string_from_utf32(Variant &r_ret, Variant &p_self, const Variant **p_args) { + PoolByteArray *ba = reinterpret_cast(p_self._data._mem); + String s; + if (ba->size() > 0) { + PoolByteArray::Read r = ba->read(); + s = String((const CharType *)r.ptr(), ba->size() / 4); + } + r_ret = s; + } + static void _call_PoolByteArray_compress(Variant &r_ret, Variant &p_self, const Variant **p_args) { PoolByteArray *ba = reinterpret_cast(p_self._data._mem); PoolByteArray compressed; @@ -2267,6 +2320,8 @@ void register_variant_methods() { ADDFUNC0R(STRING, POOL_BYTE_ARRAY, String, to_ascii, varray()); ADDFUNC0R(STRING, POOL_BYTE_ARRAY, String, to_utf8, varray()); + ADDFUNC0R(STRING, POOL_BYTE_ARRAY, String, to_utf16, varray()); + ADDFUNC0R(STRING, POOL_BYTE_ARRAY, String, to_utf32, varray()); ADDFUNC0R(STRING, POOL_BYTE_ARRAY, String, to_wchar, varray()); ADDFUNC1(VECTOR2, NIL, Vector2, set_all, REAL, "value", varray()); @@ -2599,6 +2654,8 @@ void register_variant_methods() { ADDFUNC0R(POOL_BYTE_ARRAY, STRING, PoolByteArray, get_string_from_ascii, varray()); ADDFUNC0R(POOL_BYTE_ARRAY, STRING, PoolByteArray, get_string_from_utf8, varray()); + ADDFUNC0R(POOL_BYTE_ARRAY, STRING, PoolByteArray, get_string_from_utf16, varray()); + ADDFUNC0R(POOL_BYTE_ARRAY, STRING, PoolByteArray, get_string_from_utf32, varray()); ADDFUNC0R(POOL_BYTE_ARRAY, STRING, PoolByteArray, hex_encode, varray()); ADDFUNC1R(POOL_BYTE_ARRAY, POOL_BYTE_ARRAY, PoolByteArray, compress, INT, "compression_mode", varray(0)); ADDFUNC2R(POOL_BYTE_ARRAY, POOL_BYTE_ARRAY, PoolByteArray, decompress, INT, "buffer_size", INT, "compression_mode", varray(0)); diff --git a/doc/classes/PoolByteArray.xml b/doc/classes/PoolByteArray.xml index 9aad6dcf4..4370b3ff2 100644 --- a/doc/classes/PoolByteArray.xml +++ b/doc/classes/PoolByteArray.xml @@ -98,13 +98,25 @@ - Returns a copy of the array's contents as [String]. Fast alternative to [method get_string_from_utf8] if the content is ASCII-only. Unlike the UTF-8 function this function maps every byte to a character in the array. Multibyte sequences will not be interpreted correctly. For parsing user input always use [method get_string_from_utf8]. + Converts ASCII/Latin-1 encoded array to [String]. Fast alternative to [method get_string_from_utf8] if the content is ASCII/Latin-1 only. Unlike the UTF-8 function this function maps every byte to a character in the array. Multibyte sequences will not be interpreted correctly. For parsing user input always use [method get_string_from_utf8]. + + + + + Converts UTF-16 encoded array to [String]. If the BOM is missing, system endianness is assumed. Returns empty string if source array is not vaild UTF-16 string. + + + + + + + Converts UTF-32 encoded array to [String]. System endianness is assumed. Returns empty string if source array is not vaild UTF-32 string. - Returns a copy of the array's contents as [String]. Slower than [method get_string_from_ascii] but supports UTF-8 encoded data. Use this function if you are unsure about the source of the data. For user input this function should always be preferred. + Converts UTF-8 encoded array to [String]. Slower than [method get_string_from_ascii] but supports UTF-8 encoded data. Use this function if you are unsure about the source of the data. For user input this function should always be preferred. Returns empty string if source array is not vaild UTF-8 string. diff --git a/doc/classes/String.xml b/doc/classes/String.xml index 51d57ff6c..42431256b 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -583,6 +583,17 @@ Returns the string's amount of characters. + + + + + + + + + Formats a string to be at least [code]min_length[/code] long by adding [code]character[/code]s to the left of the string. + + @@ -724,6 +735,17 @@ Returns the right side of the string from a given position. + + + + + + + + + Formats a string to be at least [code]min_length[/code] long by adding [code]character[/code]s to the right of the string. + + @@ -848,7 +870,7 @@ - Converts the String (which is a character array) to [PoolByteArray] (which is an array of bytes). The conversion is faster compared to [method to_utf8], as this method assumes that all the characters in the String are ASCII characters. + Converts the String (which is a character array) to ASCII/Latin-1 encoded [PoolByteArray] (which is an array of bytes). The conversion is faster compared to [method to_utf8], as this method assumes that all the characters in the String are ASCII/Latin-1 characters, unsupported characters are replaced with spaces. @@ -886,10 +908,24 @@ Returns the string converted to uppercase. + + + + + Converts the String (which is an array of characters) to UTF-16 encoded [PoolByteArray] (which is an array of bytes). + + + + + + + Converts the String (which is an array of characters) to UTF-32 encoded [PoolByteArray] (which is an array of bytes). + + - Converts the String (which is an array of characters) to [PoolByteArray] (which is an array of bytes). The conversion is a bit slower than [method to_ascii], but supports all UTF-8 characters. Therefore, you should prefer this function over [method to_ascii]. + Converts the String (which is an array of characters) to UTF-8 encode [PoolByteArray] (which is an array of bytes). The conversion is a bit slower than [method to_ascii], but supports all UTF-8 characters. Therefore, you should prefer this function over [method to_ascii]. diff --git a/drivers/unix/file_access_unix.cpp b/drivers/unix/file_access_unix.cpp index 79366c253..21618db84 100644 --- a/drivers/unix/file_access_unix.cpp +++ b/drivers/unix/file_access_unix.cpp @@ -78,7 +78,7 @@ Error FileAccessUnix::_open(const String &p_path, int p_mode_flags) { path_src = p_path; path = fix_path(p_path); - //printf("opening %ls, %i\n", path.c_str(), Memory::get_static_mem_usage()); + //printf("opening %s, %i\n", path.utf8().get_data(), Memory::get_static_mem_usage()); ERR_FAIL_COND_V_MSG(f, ERR_ALREADY_IN_USE, "File is already in use."); const char *mode_string; diff --git a/drivers/windows/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp index f1324796c..49c6d3984 100644 --- a/drivers/windows/dir_access_windows.cpp +++ b/drivers/windows/dir_access_windows.cpp @@ -66,19 +66,20 @@ Error DirAccessWindows::list_dir_begin() { _cishidden = false; list_dir_end(); - p->h = FindFirstFileExW((current_dir + "\\*").c_str(), FindExInfoStandard, &p->fu, FindExSearchNameMatch, NULL, 0); + p->h = FindFirstFileExW((LPCWSTR)(String(current_dir + "\\*").utf16().get_data()), FindExInfoStandard, &p->fu, FindExSearchNameMatch, NULL, 0); return (p->h == INVALID_HANDLE_VALUE) ? ERR_CANT_OPEN : OK; } String DirAccessWindows::get_next() { - if (p->h == INVALID_HANDLE_VALUE) + if (p->h == INVALID_HANDLE_VALUE) { return ""; + } _cisdir = (p->fu.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); _cishidden = (p->fu.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN); - String name = p->fu.cFileName; + String name = String::utf16((const char16_t *)(p->fu.cFileName)); if (FindNextFileW(p->h, &p->fu) == 0) { FindClose(p->h); @@ -106,8 +107,9 @@ int DirAccessWindows::get_drive_count() { return drive_count; } String DirAccessWindows::get_drive(int p_drive) { - if (p_drive < 0 || p_drive >= drive_count) + if (p_drive < 0 || p_drive >= drive_count) { return ""; + } return String::chr(drives[p_drive]) + ":"; } @@ -117,18 +119,17 @@ Error DirAccessWindows::change_dir(String p_dir) { p_dir = fix_path(p_dir); - wchar_t real_current_dir_name[2048]; + WCHAR real_current_dir_name[2048]; GetCurrentDirectoryW(2048, real_current_dir_name); - String prev_dir = real_current_dir_name; + String prev_dir = String::utf16((const char16_t *)real_current_dir_name); - SetCurrentDirectoryW(current_dir.c_str()); - bool worked = (SetCurrentDirectoryW(p_dir.c_str()) != 0); + SetCurrentDirectoryW((LPCWSTR)(current_dir.utf16().get_data())); + bool worked = (SetCurrentDirectoryW((LPCWSTR)(p_dir.utf16().get_data())) != 0); String base = _get_root_path(); if (base != "") { GetCurrentDirectoryW(2048, real_current_dir_name); - String new_dir; - new_dir = String(real_current_dir_name).replace("\\", "/"); + String new_dir = String::utf16((const char16_t *)real_current_dir_name).replace("\\", "/"); if (!new_dir.begins_with(base)) { worked = false; } @@ -136,13 +137,11 @@ Error DirAccessWindows::change_dir(String p_dir) { if (worked) { GetCurrentDirectoryW(2048, real_current_dir_name); - current_dir = real_current_dir_name; // TODO, utf8 parser + current_dir = String::utf16((const char16_t *)real_current_dir_name); current_dir = current_dir.replace("\\", "/"); + } - } //else { - - SetCurrentDirectoryW(prev_dir.c_str()); - //} + SetCurrentDirectoryW((LPCWSTR)(prev_dir.utf16().get_data())); return worked ? OK : ERR_INVALID_PARAMETER; } @@ -151,8 +150,10 @@ Error DirAccessWindows::make_dir(String p_dir) { GLOBAL_LOCK_FUNCTION p_dir = fix_path(p_dir); - if (p_dir.is_rel_path()) + + if (p_dir.is_rel_path()) { p_dir = current_dir.plus_file(p_dir); + } p_dir = p_dir.replace("/", "\\"); @@ -165,16 +166,16 @@ Error DirAccessWindows::make_dir(String p_dir) { // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa363855(v=vs.85).aspx } - success = CreateDirectoryW(p_dir.c_str(), NULL); + success = CreateDirectoryW((LPCWSTR)(p_dir.utf16().get_data()), NULL); err = GetLastError(); if (success) { return OK; - }; + } if (err == ERROR_ALREADY_EXISTS || err == ERROR_ACCESS_DENIED) { return ERR_ALREADY_EXISTS; - }; + } return ERR_CANT_CREATE; } @@ -183,12 +184,11 @@ String DirAccessWindows::get_current_dir() { String base = _get_root_path(); if (base != "") { String bd = current_dir.replace("\\", "/").replace_first(base, ""); - if (bd.begins_with("/")) + if (bd.begins_with("/")) { return _get_root_string() + bd.substr(1, bd.length()); - else + } else { return _get_root_string() + bd; - - } else { + } } return current_dir; @@ -210,20 +210,18 @@ String DirAccessWindows::get_current_dir_without_drive() { bool DirAccessWindows::file_exists(String p_file) { GLOBAL_LOCK_FUNCTION - if (!p_file.is_abs_path()) + if (!p_file.is_abs_path()) { p_file = get_current_dir().plus_file(p_file); + } p_file = fix_path(p_file); - //p_file.replace("/","\\"); - - //WIN32_FILE_ATTRIBUTE_DATA fileInfo; - DWORD fileAttr; - fileAttr = GetFileAttributesW(p_file.c_str()); - if (INVALID_FILE_ATTRIBUTES == fileAttr) + fileAttr = GetFileAttributesW((LPCWSTR)(p_file.utf16().get_data())); + if (INVALID_FILE_ATTRIBUTES == fileAttr) { return false; + } return !(fileAttr & FILE_ATTRIBUTE_DIRECTORY); } @@ -236,26 +234,26 @@ bool DirAccessWindows::dir_exists(String p_dir) { p_dir = fix_path(p_dir); - //p_dir.replace("/","\\"); - - //WIN32_FILE_ATTRIBUTE_DATA fileInfo; - DWORD fileAttr; - fileAttr = GetFileAttributesW(p_dir.c_str()); - if (INVALID_FILE_ATTRIBUTES == fileAttr) + fileAttr = GetFileAttributesW((LPCWSTR)(p_dir.utf16().get_data())); + if (INVALID_FILE_ATTRIBUTES == fileAttr) { return false; + } + return (fileAttr & FILE_ATTRIBUTE_DIRECTORY); } Error DirAccessWindows::rename(String p_path, String p_new_path) { - if (p_path.is_rel_path()) + if (p_path.is_rel_path()) { p_path = get_current_dir().plus_file(p_path); + } p_path = fix_path(p_path); - if (p_new_path.is_rel_path()) + if (p_new_path.is_rel_path()) { p_new_path = get_current_dir().plus_file(p_new_path); + } p_new_path = fix_path(p_new_path); @@ -263,21 +261,22 @@ Error DirAccessWindows::rename(String p_path, String p_new_path) { if (p_path.to_lower() == p_new_path.to_lower()) { if (dir_exists(p_path)) { // The path is a dir; just rename - return ::_wrename(p_path.c_str(), p_new_path.c_str()) == 0 ? OK : FAILED; + return ::_wrename((LPCWSTR)(p_path.utf16().get_data()), (LPCWSTR)(p_new_path.utf16().get_data())) == 0 ? OK : FAILED; } + // The path is a file; juggle WCHAR tmpfile[MAX_PATH]; - if (!GetTempFileNameW(fix_path(get_current_dir()).c_str(), NULL, 0, tmpfile)) { + if (!GetTempFileNameW((LPCWSTR)(fix_path(get_current_dir()).utf16().get_data()), NULL, 0, tmpfile)) { return FAILED; } - if (!::ReplaceFileW(tmpfile, p_path.c_str(), NULL, 0, NULL, NULL)) { + if (!::ReplaceFileW(tmpfile, (LPCWSTR)(p_path.utf16().get_data()), NULL, 0, NULL, NULL)) { DeleteFileW(tmpfile); return FAILED; } - return ::_wrename(tmpfile, p_new_path.c_str()) == 0 ? OK : FAILED; + return ::_wrename(tmpfile, (LPCWSTR)(p_new_path.utf16().get_data())) == 0 ? OK : FAILED; } else { if (file_exists(p_new_path)) { @@ -286,59 +285,61 @@ Error DirAccessWindows::rename(String p_path, String p_new_path) { } } - return ::_wrename(p_path.c_str(), p_new_path.c_str()) == 0 ? OK : FAILED; + return ::_wrename((LPCWSTR)(p_path.utf16().get_data()), (LPCWSTR)(p_new_path.utf16().get_data())) == 0 ? OK : FAILED; } } Error DirAccessWindows::remove(String p_path) { - if (p_path.is_rel_path()) + if (p_path.is_rel_path()) { p_path = get_current_dir().plus_file(p_path); + } p_path = fix_path(p_path); DWORD fileAttr; - fileAttr = GetFileAttributesW(p_path.c_str()); - if (INVALID_FILE_ATTRIBUTES == fileAttr) + fileAttr = GetFileAttributesW((LPCWSTR)(p_path.utf16().get_data())); + if (INVALID_FILE_ATTRIBUTES == fileAttr) { return FAILED; - if ((fileAttr & FILE_ATTRIBUTE_DIRECTORY)) - return ::_wrmdir(p_path.c_str()) == 0 ? OK : FAILED; - else - return ::_wunlink(p_path.c_str()) == 0 ? OK : FAILED; + } + + if ((fileAttr & FILE_ATTRIBUTE_DIRECTORY)) { + return ::_wrmdir((LPCWSTR)(p_path.utf16().get_data())) == 0 ? OK : FAILED; + } else { + return ::_wunlink((LPCWSTR)(p_path.utf16().get_data())) == 0 ? OK : FAILED; + } } /* FileType DirAccessWindows::get_file_type(const String& p_file) const { + WCHAR real_current_dir_name[2048]; + GetCurrentDirectoryW(2048, real_current_dir_name); + String prev_dir = Strong::utf16((const char16_t *)real_current_dir_name); - - wchar_t real_current_dir_name[2048]; - GetCurrentDirectoryW(2048,real_current_dir_name); - String prev_dir=real_current_dir_name; - - bool worked SetCurrentDirectoryW(current_dir.c_str()); + bool worked = SetCurrentDirectoryW((LPCWSTR)(current_dir.utf16().get_data())); DWORD attr; if (worked) { - - WIN32_FILE_ATTRIBUTE_DATA fileInfo; - attr = GetFileAttributesExW(p_file.c_str(), GetFileExInfoStandard, &fileInfo); - + WIN32_FILE_ATTRIBUTE_DATA fileInfo; + attr = GetFileAttributesExW((LPCWSTR)(p_file.utf16().get_data()), GetFileExInfoStandard, &fileInfo); } - SetCurrentDirectoryW(prev_dir.c_str()); + SetCurrentDirectoryW((LPCWSTR)(prev_dir.utf16().get_data())); - if (!worked) + if (!worked) { return FILE_TYPE_NONE; + } - - return (attr&FILE_ATTRIBUTE_DIRECTORY)?FILE_TYPE_ + return (attr & FILE_ATTRIBUTE_DIRECTORY) ? FILE_TYPE_ } */ uint64_t DirAccessWindows::get_space_left() { uint64_t bytes = 0; - if (!GetDiskFreeSpaceEx(NULL, (PULARGE_INTEGER)&bytes, NULL, NULL)) + + if (!GetDiskFreeSpaceEx(NULL, (PULARGE_INTEGER)&bytes, NULL, NULL)) { return 0; + } //this is either 0 or a value in bytes. return bytes; @@ -347,7 +348,7 @@ uint64_t DirAccessWindows::get_space_left() { String DirAccessWindows::get_filesystem_type() const { String path = fix_path(const_cast(this)->get_current_dir()); - if (path.is_network_share_path()) { + if (path.is_network_share_path()) { return "Network Share"; } @@ -361,7 +362,7 @@ String DirAccessWindows::get_filesystem_type() const { DWORD dwMaxFileNameLength = 0; DWORD dwFileSystemFlags = 0; - if (::GetVolumeInformationW(unit.c_str(), + if (::GetVolumeInformationW((LPCWSTR)(unit.utf16().get_data()), szVolumeName, sizeof(szVolumeName), &dwSerialNumber, @@ -369,7 +370,7 @@ String DirAccessWindows::get_filesystem_type() const { &dwFileSystemFlags, szFileSystemName, sizeof(szFileSystemName)) == TRUE) { - return String(szFileSystemName); + return String::utf16((const char16_t *)szFileSystemName); } ERR_FAIL_V(""); diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp index 7573e94de..4802afe5f 100644 --- a/drivers/windows/file_access_windows.cpp +++ b/drivers/windows/file_access_windows.cpp @@ -61,29 +61,33 @@ void FileAccessWindows::check_errors() const { Error FileAccessWindows::_open(const String &p_path, int p_mode_flags) { path_src = p_path; path = fix_path(p_path); - if (f) + + if (f) { close(); + } - const wchar_t *mode_string; + const WCHAR *mode_string; - if (p_mode_flags == READ) + if (p_mode_flags == READ) { mode_string = L"rb"; - else if (p_mode_flags == WRITE) + } else if (p_mode_flags == WRITE) { mode_string = L"wb"; - else if (p_mode_flags == READ_WRITE) + } else if (p_mode_flags == READ_WRITE) { mode_string = L"rb+"; - else if (p_mode_flags == WRITE_READ) + } else if (p_mode_flags == WRITE_READ) { mode_string = L"wb+"; - else + } else { return ERR_INVALID_PARAMETER; + } /* pretty much every implementation that uses fopen as primary backend supports utf8 encoding */ struct _stat st; - if (_wstat(path.c_str(), &st) == 0) { - if (!S_ISREG(st.st_mode)) + if (_wstat((LPCWSTR)(path.utf16().get_data()), &st) == 0) { + if (!S_ISREG(st.st_mode)) { return ERR_FILE_CANT_OPEN; + } }; #ifdef TOOLS_ENABLED @@ -93,15 +97,18 @@ Error FileAccessWindows::_open(const String &p_path, int p_mode_flags) { // platforms). if (p_mode_flags == READ) { WIN32_FIND_DATAW d; - HANDLE f = FindFirstFileW(path.c_str(), &d); + HANDLE f = FindFirstFileW((LPCWSTR)(path.utf16().get_data()), &d); + if (f != INVALID_HANDLE_VALUE) { - String fname = d.cFileName; + String fname = String::utf16((const char16_t *)(d.cFileName)); + if (fname != String()) { String base_file = path.get_file(); if (base_file != fname && base_file.findn(fname) == 0) { WARN_PRINT("Case mismatch opening requested file '" + base_file + "', stored as '" + fname + "' in the filesystem. This file will not open when exported to other case-sensitive platforms."); } } + FindClose(f); } } @@ -112,7 +119,7 @@ Error FileAccessWindows::_open(const String &p_path, int p_mode_flags) { path = path + ".tmp"; } - f = _wfsopen((LPCWSTR)(path.c_str()), mode_string, _SH_DENYNO); + f = _wfsopen((LPCWSTR)(path.utf16().get_data()), mode_string, _SH_DENYNO); if (f == nullptr) { switch (errno) { @@ -132,8 +139,9 @@ Error FileAccessWindows::_open(const String &p_path, int p_mode_flags) { } void FileAccessWindows::close() { - if (!f) + if (!f) { return; + } fclose(f); f = NULL; @@ -150,16 +158,16 @@ void FileAccessWindows::close() { // UWP has no PathFileExists, so we check attributes instead DWORD fileAttr; - fileAttr = GetFileAttributesW(save_path.c_str()); + fileAttr = GetFileAttributesW((LPCWSTR)(save_path.utf16().get_data())); if (INVALID_FILE_ATTRIBUTES == fileAttr) { #else - if (!PathFileExistsW(save_path.c_str())) { + if (!PathFileExistsW((LPCWSTR)(save_path.utf16().get_data()))) { #endif //creating new file - rename_error = _wrename((save_path + ".tmp").c_str(), save_path.c_str()) != 0; + rename_error = _wrename((LPCWSTR)((save_path + ".tmp").utf16().get_data()), (LPCWSTR)(save_path.utf16().get_data())) != 0; } else { //atomic replace for existing file - rename_error = !ReplaceFileW(save_path.c_str(), (save_path + ".tmp").c_str(), NULL, 2 | 4, NULL, NULL); + rename_error = !ReplaceFileW((LPCWSTR)(save_path.utf16().get_data()), (LPCWSTR)((save_path + ".tmp").utf16().get_data()), NULL, 2 | 4, NULL, NULL); } if (rename_error) { attempts--; @@ -195,23 +203,31 @@ void FileAccessWindows::seek(uint64_t p_position) { ERR_FAIL_COND(!f); last_error = OK; - if (_fseeki64(f, p_position, SEEK_SET)) + + if (_fseeki64(f, p_position, SEEK_SET)) { check_errors(); + } + prev_op = 0; } void FileAccessWindows::seek_end(int64_t p_position) { ERR_FAIL_COND(!f); - if (_fseeki64(f, p_position, SEEK_END)) + + if (_fseeki64(f, p_position, SEEK_END)) { check_errors(); + } + prev_op = 0; } uint64_t FileAccessWindows::get_position() const { int64_t aux_position = _ftelli64(f); + if (aux_position < 0) { check_errors(); } + return aux_position; } @@ -270,8 +286,10 @@ Error FileAccessWindows::get_error() const { void FileAccessWindows::flush() { ERR_FAIL_COND(!f); fflush(f); - if (prev_op == WRITE) + + if (prev_op == WRITE) { prev_op = 0; + } } void FileAccessWindows::store_8(uint8_t p_dest) { @@ -290,6 +308,7 @@ void FileAccessWindows::store_8(uint8_t p_dest) { void FileAccessWindows::store_buffer(const uint8_t *p_src, uint64_t p_length) { ERR_FAIL_COND(!f); ERR_FAIL_COND(!p_src && p_length > 0); + if (flags == READ_WRITE || flags == WRITE_READ) { if (prev_op == READ) { if (last_error != ERR_FILE_EOF) { @@ -303,7 +322,9 @@ void FileAccessWindows::store_buffer(const uint8_t *p_src, uint64_t p_length) { bool FileAccessWindows::file_exists(const String &p_name) { String filename = fix_path(p_name); - FILE *g = _wfsopen((LPCWSTR)(filename.c_str()), L"rb", _SH_DENYNO); + + FILE *g = _wfsopen((LPCWSTR)(filename.utf16().get_data()), L"rb", _SH_DENYNO); + if (g == nullptr) { return false; } else { @@ -318,7 +339,7 @@ uint64_t FileAccessWindows::_get_modified_time(const String &p_file) { file = file.substr(0, file.length() - 1); struct _stat st; - int rv = _wstat(file.c_str(), &st); + int rv = _wstat((LPCWSTR)(file.utf16().get_data()), &st); if (rv == 0) { return st.st_mtime; diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp index e04fdca0b..56e970bcb 100644 --- a/editor/editor_export.cpp +++ b/editor/editor_export.cpp @@ -506,8 +506,8 @@ void EditorExportPlatform::_edit_files_with_filter(DirAccess *da, const Vector dirs; - String f; - while ((f = da->get_next()) != "") { + String f = da->get_next(); + while (!f.empty()) { if (da->current_is_dir()) { dirs.push_back(f); } else { @@ -524,6 +524,7 @@ void EditorExportPlatform::_edit_files_with_filter(DirAccess *da, const Vectorget_next(); } da->list_dir_end(); diff --git a/editor/editor_file_dialog.cpp b/editor/editor_file_dialog.cpp index cfc614c89..42d18ce59 100644 --- a/editor/editor_file_dialog.cpp +++ b/editor/editor_file_dialog.cpp @@ -787,10 +787,11 @@ void EditorFileDialog::update_file_list() { List files; List dirs; - String item; + String item = dir_access->get_next(); - while ((item = dir_access->get_next()) != "") { + while (!item.empty()) { if (item == "." || item == "..") { + item = dir_access->get_next(); continue; } @@ -808,6 +809,8 @@ void EditorFileDialog::update_file_list() { files.push_back(item); } } + + item = dir_access->get_next(); } dirs.sort_custom(); diff --git a/editor/editor_run.cpp b/editor/editor_run.cpp index 06f342bc9..bd2ccaaff 100644 --- a/editor/editor_run.cpp +++ b/editor/editor_run.cpp @@ -241,9 +241,9 @@ Error EditorRun::run(const String &p_scene, const String &p_custom_args, const L } } - printf("Running: %ls", exec.c_str()); + printf("Running: %s", exec.utf8().get_data()); for (List::Element *E = args.front(); E; E = E->next()) { - printf(" %ls", E->get().c_str()); + printf(" %s", E->get().utf8().get_data()); }; printf("\n"); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index a464489d1..59e326e2c 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -6601,7 +6601,7 @@ void CanvasItemEditorViewport::_perform_drop_data() { files_str += error_files[i].get_file().get_basename() + ","; } files_str = files_str.substr(0, files_str.length() - 1); - accept->set_text(vformat(TTR("Error instancing scene from %s"), files_str.c_str())); + accept->set_text(vformat(TTR("Error instancing scene from %s"), files_str)); accept->popup_centered_minsize(); } } diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp index e6cc8442b..e4f50fa73 100644 --- a/editor/plugins/spatial_editor_plugin.cpp +++ b/editor/plugins/spatial_editor_plugin.cpp @@ -4266,7 +4266,7 @@ void SpatialEditorViewport::_perform_drop_data() { files_str += error_files[i].get_file().get_basename() + ","; } files_str = files_str.substr(0, files_str.length() - 1); - accept->set_text(vformat(TTR("Error instancing scene from %s"), files_str.c_str())); + accept->set_text(vformat(TTR("Error instancing scene from %s"), files_str)); accept->popup_centered_minsize(); } } diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index 3d493eafb..9008a98c0 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -215,7 +215,7 @@ void ProjectSettingsEditor::_notification(int p_what) { } static bool _validate_action_name(const String &p_name) { - const CharType *cstr = p_name.c_str(); + const CharType *cstr = p_name.get_data(); for (int i = 0; cstr[i]; i++) { if (cstr[i] == '/' || cstr[i] == ':' || cstr[i] == '"' || cstr[i] == '=' || cstr[i] == '\\' || cstr[i] < 32) { diff --git a/main/tests/test_basis.cpp b/main/tests/test_basis.cpp index 15d90d858..dd9d56180 100644 --- a/main/tests/test_basis.cpp +++ b/main/tests/test_basis.cpp @@ -165,15 +165,15 @@ bool test_rotation(Vector3 deg_original_euler, RotOrder rot_order) { Basis res = to_rotation.inverse() * rotation_from_computed_euler; if ((res.get_axis(0) - Vector3(1.0, 0.0, 0.0)).length() > 0.1) { - OS::get_singleton()->print("Fail due to X %ls\n", String(res.get_axis(0)).c_str()); + OS::get_singleton()->print("Fail due to X %s\n", String(res.get_axis(0)).utf8().get_data()); pass = false; } if ((res.get_axis(1) - Vector3(0.0, 1.0, 0.0)).length() > 0.1) { - OS::get_singleton()->print("Fail due to Y %ls\n", String(res.get_axis(1)).c_str()); + OS::get_singleton()->print("Fail due to Y %s\n", String(res.get_axis(1)).utf8().get_data()); pass = false; } if ((res.get_axis(2) - Vector3(0.0, 0.0, 1.0)).length() > 0.1) { - OS::get_singleton()->print("Fail due to Z %ls\n", String(res.get_axis(2)).c_str()); + OS::get_singleton()->print("Fail due to Z %s\n", String(res.get_axis(2)).utf8().get_data()); pass = false; } @@ -186,15 +186,15 @@ bool test_rotation(Vector3 deg_original_euler, RotOrder rot_order) { res = to_rotation.inverse() * rotation_from_xyz_computed_euler; if ((res.get_axis(0) - Vector3(1.0, 0.0, 0.0)).length() > 0.1) { - OS::get_singleton()->print("Double check with XYZ rot order failed, due to X %ls\n", String(res.get_axis(0)).c_str()); + OS::get_singleton()->print("Double check with XYZ rot order failed, due to X %s\n", String(res.get_axis(0)).utf8().get_data()); pass = false; } if ((res.get_axis(1) - Vector3(0.0, 1.0, 0.0)).length() > 0.1) { - OS::get_singleton()->print("Double check with XYZ rot order failed, due to Y %ls\n", String(res.get_axis(1)).c_str()); + OS::get_singleton()->print("Double check with XYZ rot order failed, due to Y %s\n", String(res.get_axis(1)).utf8().get_data()); pass = false; } if ((res.get_axis(2) - Vector3(0.0, 0.0, 1.0)).length() > 0.1) { - OS::get_singleton()->print("Double check with XYZ rot order failed, due to Z %ls\n", String(res.get_axis(2)).c_str()); + OS::get_singleton()->print("Double check with XYZ rot order failed, due to Z %s\n", String(res.get_axis(2)).utf8().get_data()); pass = false; } } @@ -202,9 +202,9 @@ bool test_rotation(Vector3 deg_original_euler, RotOrder rot_order) { if (pass == false) { // Print phase only if not pass. OS *os = OS::get_singleton(); - os->print("Rotation order: %ls\n.", get_rot_order_name(rot_order).c_str()); - os->print("Original Rotation: %ls\n", String(deg_original_euler).c_str()); - os->print("Quaternionernion to rotation order: %ls\n", String(rad2deg(euler_from_rotation)).c_str()); + os->print("Rotation order: %s\n.", get_rot_order_name(rot_order).utf8().get_data()); + os->print("Original Rotation: %s\n", String(deg_original_euler).utf8().get_data()); + os->print("Quaternionernion to rotation order: %s\n", String(rad2deg(euler_from_rotation)).utf8().get_data()); } return pass; @@ -302,9 +302,9 @@ void test_euler_conversion() { } if (failed == 0) { - OS::get_singleton()->print("%i passed tests for rotation order: %ls.\n", passed, get_rot_order_name(rotorder_to_test[h]).c_str()); + OS::get_singleton()->print("%i passed tests for rotation order: %s.\n", passed, get_rot_order_name(rotorder_to_test[h]).utf8().get_data()); } else { - OS::get_singleton()->print("%i FAILED tests for rotation order: %ls.\n", failed, get_rot_order_name(rotorder_to_test[h]).c_str()); + OS::get_singleton()->print("%i FAILED tests for rotation order: %s.\n", failed, get_rot_order_name(rotorder_to_test[h]).utf8().get_data()); } } diff --git a/main/tests/test_main.cpp b/main/tests/test_main.cpp index 9dab7ee3a..91fd56282 100644 --- a/main/tests/test_main.cpp +++ b/main/tests/test_main.cpp @@ -53,7 +53,7 @@ const char **tests_get_names() { static const char *test_names[] = { - "string", + //"string", "math", "basis", "transform", @@ -78,9 +78,9 @@ const char **tests_get_names() { } MainLoop *test_main(String p_test, const List &p_args) { - if (p_test == "string") { - return TestString::test(); - } + //if (p_test == "string") { + // return TestString::test(); + //} if (p_test == "math") { return TestMath::test(); diff --git a/main/tests/test_string.cpp b/main/tests/test_string.cpp.old similarity index 78% rename from main/tests/test_string.cpp rename to main/tests/test_string.cpp.old index 502c6a3b9..64fd211d2 100644 --- a/main/tests/test_string.cpp +++ b/main/tests/test_string.cpp.old @@ -43,16 +43,16 @@ #include namespace TestString { - +/* bool test_1() { OS::get_singleton()->print("\n\nTest 1: Assign from cstr\n"); String s = "Hello"; OS::get_singleton()->print("\tExpected: Hello\n"); - OS::get_singleton()->print("\tResulted: %ls\n", s.c_str()); + OS::get_singleton()->print("\tResulted: %s\n", s.utf8().get_data()); - return (wcscmp(s.c_str(), L"Hello") == 0); + return (wcscmp(s.get_data(), U"Hello") == 0); } bool test_2() { @@ -62,9 +62,9 @@ bool test_2() { const String &t = s; OS::get_singleton()->print("\tExpected: Dolly\n"); - OS::get_singleton()->print("\tResulted: %ls\n", t.c_str()); + OS::get_singleton()->print("\tResulted: %s\n", t.utf8().get_data()); - return (wcscmp(t.c_str(), L"Dolly") == 0); + return (wcscmp(t.get_data(), U"Dolly") == 0); } bool test_3() { @@ -74,9 +74,9 @@ bool test_3() { const String &t(s); OS::get_singleton()->print("\tExpected: Sheep\n"); - OS::get_singleton()->print("\tResulted: %ls\n", t.c_str()); + OS::get_singleton()->print("\tResulted: %s\n", t.utf8().get_data()); - return (wcscmp(t.c_str(), L"Sheep") == 0); + return (wcscmp(t.get_data(), U"Sheep") == 0); } bool test_4() { @@ -85,9 +85,9 @@ bool test_4() { String s(L"Give me"); OS::get_singleton()->print("\tExpected: Give me\n"); - OS::get_singleton()->print("\tResulted: %ls\n", s.c_str()); + OS::get_singleton()->print("\tResulted: %s\n", s.utf8().get_data()); - return (wcscmp(s.c_str(), L"Give me") == 0); + return (wcscmp(s.get_data(), U"Give me") == 0); } bool test_5() { @@ -96,9 +96,9 @@ bool test_5() { String s(L"Wool"); OS::get_singleton()->print("\tExpected: Wool\n"); - OS::get_singleton()->print("\tResulted: %ls\n", s.c_str()); + OS::get_singleton()->print("\tResulted: %s\n", s.utf8().get_data()); - return (wcscmp(s.c_str(), L"Wool") == 0); + return (wcscmp(s.get_data(), U"Wool") == 0); } bool test_6() { @@ -112,7 +112,7 @@ bool test_6() { return false; } - if (!(s == L"Test Compare")) { + if (!(s == U"Test Compare")) { return false; } @@ -247,9 +247,9 @@ bool test_13() { //static const wchar_t ustr[] = { 'P', 0xCE, 'p',0xD3, 0 }; String s = ustr; - OS::get_singleton()->print("\tUnicode: %ls\n", ustr); + OS::get_singleton()->print("\tUnicode: %s\n", ustr); s.parse_utf8(s.utf8().get_data()); - OS::get_singleton()->print("\tConvert/Parse UTF8: %ls\n", s.c_str()); + OS::get_singleton()->print("\tConvert/Parse UTF8: %s\n", s.get_data()); return (s == ustr); } @@ -268,7 +268,7 @@ bool test_15() { OS::get_singleton()->print("\n\nTest 15: substr\n"); String s = "Killer Baby"; - OS::get_singleton()->print("\tsubstr(3,4) of \"%ls\" is \"%ls\"\n", s.c_str(), s.substr(3, 4).c_str()); + OS::get_singleton()->print("\tsubstr(3,4) of \"%s\" is \"%s\"\n", s.get_data(), s.substr(3, 4).get_data()); return (s.substr(3, 4) == "ler "); } @@ -277,7 +277,7 @@ bool test_16() { OS::get_singleton()->print("\n\nTest 16: find\n"); String s = "Pretty Woman"; - OS::get_singleton()->print("\tString: %ls\n", s.c_str()); + OS::get_singleton()->print("\tString: %s\n", s.get_data()); OS::get_singleton()->print("\t\"tty\" is at %i pos.\n", s.find("tty")); OS::get_singleton()->print("\t\"Revenge of the Monster Truck\" is at %i pos.\n", s.find("Revenge of the Monster Truck")); @@ -296,7 +296,7 @@ bool test_17() { OS::get_singleton()->print("\n\nTest 17: find no case\n"); String s = "Pretty Whale"; - OS::get_singleton()->print("\tString: %ls\n", s.c_str()); + OS::get_singleton()->print("\tString: %s\n", s.get_data()); OS::get_singleton()->print("\t\"WHA\" is at %i pos.\n", s.findn("WHA")); OS::get_singleton()->print("\t\"Revenge of the Monster SawFish\" is at %i pos.\n", s.findn("Revenge of the Monster Truck")); @@ -315,7 +315,7 @@ bool test_18() { OS::get_singleton()->print("\n\nTest 18: find no case\n"); String s = "Pretty Whale"; - OS::get_singleton()->print("\tString: %ls\n", s.c_str()); + OS::get_singleton()->print("\tString: %s\n", s.get_data()); OS::get_singleton()->print("\t\"WHA\" is at %i pos.\n", s.findn("WHA")); OS::get_singleton()->print("\t\"Revenge of the Monster SawFish\" is at %i pos.\n", s.findn("Revenge of the Monster Truck")); @@ -334,10 +334,10 @@ bool test_19() { OS::get_singleton()->print("\n\nTest 19: Search & replace\n"); String s = "Happy Birthday, Anna!"; - OS::get_singleton()->print("\tString: %ls\n", s.c_str()); + OS::get_singleton()->print("\tString: %s\n", s.get_data()); s = s.replace("Birthday", "Halloween"); - OS::get_singleton()->print("\tReplaced Birthday/Halloween: %ls.\n", s.c_str()); + OS::get_singleton()->print("\tReplaced Birthday/Halloween: %s.\n", s.get_data()); return (s == "Happy Halloween, Anna!"); } @@ -347,9 +347,9 @@ bool test_20() { String s = "Who is Frederic?"; - OS::get_singleton()->print("\tString: %ls\n", s.c_str()); + OS::get_singleton()->print("\tString: %s\n", s.get_data()); s = s.insert(s.find("?"), " Chopin"); - OS::get_singleton()->print("\tInserted Chopin: %ls.\n", s.c_str()); + OS::get_singleton()->print("\tInserted Chopin: %s.\n", s.get_data()); return (s == "Who is Frederic Chopin?"); } @@ -358,7 +358,7 @@ bool test_21() { OS::get_singleton()->print("\n\nTest 21: Number -> String\n"); OS::get_singleton()->print("\tPi is %f\n", 33.141593); - OS::get_singleton()->print("\tPi String is %ls\n", String::num(3.141593).c_str()); + OS::get_singleton()->print("\tPi String is %s\n", String::num(3.141593).get_data()); return String::num(3.141593) == "3.141593"; } @@ -404,10 +404,10 @@ bool test_24() { const char *slices[4] = { "Mars", "Jupiter", "Saturn", "Uranus" }; - OS::get_singleton()->print("\tSlicing \"%ls\" by \"%s\"..\n", s.c_str(), ","); + OS::get_singleton()->print("\tSlicing \"%s\" by \"%s\"..\n", s.get_data(), ","); for (int i = 0; i < s.get_slice_count(","); i++) { - OS::get_singleton()->print("\t\t%i- %ls\n", i + 1, s.get_slice(",", i).c_str()); + OS::get_singleton()->print("\t\t%i- %s\n", i + 1, s.get_slice(",", i).get_data()); if (s.get_slice(",", i) != slices[i]) { return false; @@ -422,11 +422,11 @@ bool test_25() { String s = "Josephine is such a cute girl!"; - OS::get_singleton()->print("\tString: %ls\n", s.c_str()); + OS::get_singleton()->print("\tString: %s\n", s.get_data()); OS::get_singleton()->print("\tRemoving \"cute\"\n"); s.erase(s.find("cute "), String("cute ").length()); - OS::get_singleton()->print("\tResult: %ls\n", s.c_str()); + OS::get_singleton()->print("\tResult: %s\n", s.get_data()); return (s == "Josephine is such a girl!"); } @@ -440,13 +440,13 @@ bool test_26() { #else String s = "Double all the vowels."; - OS::get_singleton()->print("\tString: %ls\n", s.c_str()); + OS::get_singleton()->print("\tString: %s\n", s.get_data()); OS::get_singleton()->print("\tRepeating instances of 'aeiou' once\n"); RegEx re("(?[aeiou])"); s = re.sub(s, "$0$vowel", true); - OS::get_singleton()->print("\tResult: %ls\n", s.c_str()); + OS::get_singleton()->print("\tResult: %s\n", s.get_data()); return (s == "Doouublee aall thee vooweels."); #endif @@ -486,7 +486,7 @@ bool test_28() { OS::get_singleton()->print("\n\nTest 28: sprintf\n"); bool success, state = true; - char output_format[] = "\tTest:\t%ls => %ls (%s)\n"; + char output_format[] = "\tTest:\t%s => %s (%s)\n"; String format, output; Array args; bool error; @@ -496,7 +496,7 @@ bool test_28() { args.clear(); output = format.sprintf(args, &error); success = (output == String("fish % frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; //////// INTS @@ -507,7 +507,7 @@ bool test_28() { args.push_back(5); output = format.sprintf(args, &error); success = (output == String("fish 5 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Int left padded with zeroes. @@ -516,7 +516,7 @@ bool test_28() { args.push_back(5); output = format.sprintf(args, &error); success = (output == String("fish 00005 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Int left padded with spaces. @@ -525,7 +525,7 @@ bool test_28() { args.push_back(5); output = format.sprintf(args, &error); success = (output == String("fish 5 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Int right padded with spaces. @@ -534,7 +534,7 @@ bool test_28() { args.push_back(5); output = format.sprintf(args, &error); success = (output == String("fish 5 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Int with sign (positive). @@ -543,7 +543,7 @@ bool test_28() { args.push_back(5); output = format.sprintf(args, &error); success = (output == String("fish +5 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Negative int. @@ -552,7 +552,7 @@ bool test_28() { args.push_back(-5); output = format.sprintf(args, &error); success = (output == String("fish -5 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Negative int left padded with spaces. @@ -561,7 +561,7 @@ bool test_28() { args.push_back(-5); output = format.sprintf(args, &error); success = (output == String("fish -5 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Negative int left padded with zeros. @@ -570,7 +570,7 @@ bool test_28() { args.push_back(-5); output = format.sprintf(args, &error); success = (output == String("fish -0005 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Negative int right padded with spaces. @@ -579,7 +579,7 @@ bool test_28() { args.push_back(-5); output = format.sprintf(args, &error); success = (output == String("fish -5 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Negative int right padded with zeros. (0 ignored) @@ -588,7 +588,7 @@ bool test_28() { args.push_back(-5); output = format.sprintf(args, &error); success = (output == String("fish -5 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Hex (lower) @@ -597,7 +597,7 @@ bool test_28() { args.push_back(45); output = format.sprintf(args, &error); success = (output == String("fish 2d frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Hex (upper) @@ -606,7 +606,7 @@ bool test_28() { args.push_back(45); output = format.sprintf(args, &error); success = (output == String("fish 2D frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Octal @@ -615,7 +615,7 @@ bool test_28() { args.push_back(99); output = format.sprintf(args, &error); success = (output == String("fish 143 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; ////// REALS @@ -626,7 +626,7 @@ bool test_28() { args.push_back(99.99); output = format.sprintf(args, &error); success = (output == String("fish 99.990000 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Real left-padded @@ -635,7 +635,7 @@ bool test_28() { args.push_back(99.99); output = format.sprintf(args, &error); success = (output == String("fish 99.990000 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Real right-padded @@ -644,7 +644,7 @@ bool test_28() { args.push_back(99.99); output = format.sprintf(args, &error); success = (output == String("fish 99.990000 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Real given int. @@ -653,7 +653,7 @@ bool test_28() { args.push_back(99); output = format.sprintf(args, &error); success = (output == String("fish 99.000000 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Real with sign (positive). @@ -662,7 +662,7 @@ bool test_28() { args.push_back(99.99); output = format.sprintf(args, &error); success = (output == String("fish +99.990000 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Real with 1 decimals. @@ -671,7 +671,7 @@ bool test_28() { args.push_back(99.99); output = format.sprintf(args, &error); success = (output == String("fish 100.0 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Real with 12 decimals. @@ -680,7 +680,7 @@ bool test_28() { args.push_back(99.99); output = format.sprintf(args, &error); success = (output == String("fish 99.990000000000 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Real with no decimals. @@ -689,7 +689,7 @@ bool test_28() { args.push_back(99.99); output = format.sprintf(args, &error); success = (output == String("fish 100 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Negative real right padded with zeros. (0 ignored) @@ -698,7 +698,7 @@ bool test_28() { args.push_back(-99.99); output = format.sprintf(args, &error); success = (output == String("fish -99.990000 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; /////// Strings. @@ -709,7 +709,7 @@ bool test_28() { args.push_back("cheese"); output = format.sprintf(args, &error); success = (output == String("fish cheese frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // String left-padded @@ -718,7 +718,7 @@ bool test_28() { args.push_back("cheese"); output = format.sprintf(args, &error); success = (output == String("fish cheese frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // String right-padded @@ -727,7 +727,7 @@ bool test_28() { args.push_back("cheese"); output = format.sprintf(args, &error); success = (output == String("fish cheese frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; ///// Characters @@ -738,7 +738,7 @@ bool test_28() { args.push_back("A"); output = format.sprintf(args, &error); success = (output == String("fish A frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Character as int. @@ -747,7 +747,7 @@ bool test_28() { args.push_back(65); output = format.sprintf(args, &error); success = (output == String("fish A frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; ///// Dynamic width @@ -759,7 +759,7 @@ bool test_28() { args.push_back("cheese"); output = format.sprintf(args, &error); success = (output == String("fish cheese frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Int dynamic width @@ -769,7 +769,7 @@ bool test_28() { args.push_back(99); output = format.sprintf(args, &error); success = (output == String("fish 99 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Float dynamic width @@ -780,7 +780,7 @@ bool test_28() { args.push_back(99.99); output = format.sprintf(args, &error); success = (output == String("fish 99.990 frog") && !error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; ///// Errors @@ -791,7 +791,7 @@ bool test_28() { args.push_back("cheese"); output = format.sprintf(args, &error); success = (output == "not enough arguments for format string" && error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // More arguments than formats. @@ -801,7 +801,7 @@ bool test_28() { args.push_back("cheese"); output = format.sprintf(args, &error); success = (output == "not all arguments converted during string formatting" && error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Incomplete format. @@ -810,7 +810,7 @@ bool test_28() { args.push_back("cheese"); output = format.sprintf(args, &error); success = (output == "incomplete format" && error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Bad character in format string @@ -819,7 +819,7 @@ bool test_28() { args.push_back("cheese"); output = format.sprintf(args, &error); success = (output == "unsupported format character" && error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Too many decimals. @@ -828,7 +828,7 @@ bool test_28() { args.push_back(99.99); output = format.sprintf(args, &error); success = (output == "too many decimal points in format" && error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // * not a number @@ -838,7 +838,7 @@ bool test_28() { args.push_back(99.99); output = format.sprintf(args, &error); success = (output == "* wants number" && error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Character too long. @@ -847,7 +847,7 @@ bool test_28() { args.push_back("sc"); output = format.sprintf(args, &error); success = (output == "%c requires number or single-character string" && error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; // Character bad type. @@ -856,7 +856,7 @@ bool test_28() { args.push_back(Array()); output = format.sprintf(args, &error); success = (output == "%c requires number or single-character string" && error); - OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print(output_format, format.get_data(), output.get_data(), success ? "OK" : "FAIL"); state = state && success; return state; @@ -866,50 +866,50 @@ bool test_29() { bool state = true; IP_Address ip0("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); - OS::get_singleton()->print("ip0 is %ls\n", String(ip0).c_str()); + OS::get_singleton()->print("ip0 is %s\n", String(ip0).get_data()); IP_Address ip(0x0123, 0x4567, 0x89ab, 0xcdef, true); - OS::get_singleton()->print("ip6 is %ls\n", String(ip).c_str()); + OS::get_singleton()->print("ip6 is %s\n", String(ip).get_data()); IP_Address ip2("fe80::52e5:49ff:fe93:1baf"); - OS::get_singleton()->print("ip6 is %ls\n", String(ip2).c_str()); + OS::get_singleton()->print("ip6 is %s\n", String(ip2).get_data()); IP_Address ip3("::ffff:192.168.0.1"); - OS::get_singleton()->print("ip6 is %ls\n", String(ip3).c_str()); + OS::get_singleton()->print("ip6 is %s\n", String(ip3).get_data()); String ip4 = "192.168.0.1"; bool success = ip4.is_valid_ip_address(); - OS::get_singleton()->print("Is valid ipv4: %ls, %s\n", ip4.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Is valid ipv4: %s, %s\n", ip4.get_data(), success ? "OK" : "FAIL"); state = state && success; ip4 = "192.368.0.1"; success = (!ip4.is_valid_ip_address()); - OS::get_singleton()->print("Is invalid ipv4: %ls, %s\n", ip4.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Is invalid ipv4: %s, %s\n", ip4.get_data(), success ? "OK" : "FAIL"); state = state && success; String ip6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; success = ip6.is_valid_ip_address(); - OS::get_singleton()->print("Is valid ipv6: %ls, %s\n", ip6.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Is valid ipv6: %s, %s\n", ip6.get_data(), success ? "OK" : "FAIL"); state = state && success; ip6 = "2001:0db8:85j3:0000:0000:8a2e:0370:7334"; success = (!ip6.is_valid_ip_address()); - OS::get_singleton()->print("Is invalid ipv6: %ls, %s\n", ip6.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Is invalid ipv6: %s, %s\n", ip6.get_data(), success ? "OK" : "FAIL"); state = state && success; ip6 = "2001:0db8:85f345:0000:0000:8a2e:0370:7334"; success = (!ip6.is_valid_ip_address()); - OS::get_singleton()->print("Is invalid ipv6: %ls, %s\n", ip6.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Is invalid ipv6: %s, %s\n", ip6.get_data(), success ? "OK" : "FAIL"); state = state && success; ip6 = "2001:0db8::0:8a2e:370:7334"; success = (ip6.is_valid_ip_address()); - OS::get_singleton()->print("Is valid ipv6: %ls, %s\n", ip6.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Is valid ipv6: %s, %s\n", ip6.get_data(), success ? "OK" : "FAIL"); state = state && success; ip6 = "::ffff:192.168.0.1"; success = (ip6.is_valid_ip_address()); - OS::get_singleton()->print("Is valid ipv6: %ls, %s\n", ip6.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Is valid ipv6: %s, %s\n", ip6.get_data(), success ? "OK" : "FAIL"); state = state && success; return state; @@ -922,85 +922,85 @@ bool test_30() { String output = "Bytes 2 Var"; success = (input.capitalize() == output); state = state && success; - OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Capitalize %s: %s, %s\n", input.get_data(), output.get_data(), success ? "OK" : "FAIL"); input = "linear2db"; output = "Linear 2 Db"; success = (input.capitalize() == output); state = state && success; - OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Capitalize %s: %s, %s\n", input.get_data(), output.get_data(), success ? "OK" : "FAIL"); input = "vector3"; output = "Vector 3"; success = (input.capitalize() == output); state = state && success; - OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Capitalize %s: %s, %s\n", input.get_data(), output.get_data(), success ? "OK" : "FAIL"); input = "sha256"; output = "Sha 256"; success = (input.capitalize() == output); state = state && success; - OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Capitalize %s: %s, %s\n", input.get_data(), output.get_data(), success ? "OK" : "FAIL"); input = "2db"; output = "2 Db"; success = (input.capitalize() == output); state = state && success; - OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Capitalize %s: %s, %s\n", input.get_data(), output.get_data(), success ? "OK" : "FAIL"); input = "PascalCase"; output = "Pascal Case"; success = (input.capitalize() == output); state = state && success; - OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Capitalize %s: %s, %s\n", input.get_data(), output.get_data(), success ? "OK" : "FAIL"); input = "PascalPascalCase"; output = "Pascal Pascal Case"; success = (input.capitalize() == output); state = state && success; - OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Capitalize %s: %s, %s\n", input.get_data(), output.get_data(), success ? "OK" : "FAIL"); input = "snake_case"; output = "Snake Case"; success = (input.capitalize() == output); state = state && success; - OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Capitalize %s: %s, %s\n", input.get_data(), output.get_data(), success ? "OK" : "FAIL"); input = "snake_snake_case"; output = "Snake Snake Case"; success = (input.capitalize() == output); state = state && success; - OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Capitalize %s: %s, %s\n", input.get_data(), output.get_data(), success ? "OK" : "FAIL"); input = "sha256sum"; output = "Sha 256 Sum"; success = (input.capitalize() == output); state = state && success; - OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Capitalize %s: %s, %s\n", input.get_data(), output.get_data(), success ? "OK" : "FAIL"); input = "cat2dog"; output = "Cat 2 Dog"; success = (input.capitalize() == output); state = state && success; - OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Capitalize %s: %s, %s\n", input.get_data(), output.get_data(), success ? "OK" : "FAIL"); input = "function(name)"; output = "Function(name)"; success = (input.capitalize() == output); state = state && success; - OS::get_singleton()->print("Capitalize %ls (existing incorrect behavior): %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Capitalize %s (existing incorrect behavior): %s, %s\n", input.get_data(), output.get_data(), success ? "OK" : "FAIL"); input = "snake_case_function(snake_case_arg)"; output = "Snake Case Function(snake Case Arg)"; success = (input.capitalize() == output); state = state && success; - OS::get_singleton()->print("Capitalize %ls (existing incorrect behavior): %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Capitalize %s (existing incorrect behavior): %s, %s\n", input.get_data(), output.get_data(), success ? "OK" : "FAIL"); input = "snake_case_function( snake_case_arg )"; output = "Snake Case Function( Snake Case Arg )"; success = (input.capitalize() == output); state = state && success; - OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); + OS::get_singleton()->print("Capitalize %s: %s, %s\n", input.get_data(), output.get_data(), success ? "OK" : "FAIL"); return state; } diff --git a/main/tests/test_transform.cpp b/main/tests/test_transform.cpp index e9fc9a7a0..9984d1340 100644 --- a/main/tests/test_transform.cpp +++ b/main/tests/test_transform.cpp @@ -85,9 +85,9 @@ bool test_aabb_regular() { if (!pass) { String string = String("bb2 : ") + String(Variant(bb2)); - OS::get_singleton()->print("\t%ls\n", string.c_str()); + OS::get_singleton()->print("\t%s\n", string.utf8().get_data()); string = String("bb3 : ") + String(Variant(bb3)); - OS::get_singleton()->print("\t%ls\n", string.c_str()); + OS::get_singleton()->print("\t%s\n", string.utf8().get_data()); } return pass; @@ -133,9 +133,9 @@ bool test_aabb_non_uniform_scale() { if (!pass) { String string = String("bb2 : ") + String(Variant(bb2)); - OS::get_singleton()->print("\t%ls\n", string.c_str()); + OS::get_singleton()->print("\t%s\n", string.utf8().get_data()); string = String("bb3 : ") + String(Variant(bb3)); - OS::get_singleton()->print("\t%ls\n", string.c_str()); + OS::get_singleton()->print("\t%s\n", string.utf8().get_data()); } return pass; diff --git a/modules/database/query_builder.cpp b/modules/database/query_builder.cpp index 96c33fb36..9ad5b742c 100644 --- a/modules/database/query_builder.cpp +++ b/modules/database/query_builder.cpp @@ -288,7 +288,7 @@ void QueryBuilder::run_query() { } void QueryBuilder::print() { - //printf("%s\n", query_result.c_str()); + //printf("%s\n", query_result.get_data()); ERR_PRINT(query_result); } diff --git a/modules/database/table_builder.cpp b/modules/database/table_builder.cpp index e32defd42..87f6249d7 100644 --- a/modules/database/table_builder.cpp +++ b/modules/database/table_builder.cpp @@ -102,7 +102,7 @@ void TableBuilder::run_query() { } void TableBuilder::print() { - //printf("%s\n", result.c_str()); + //printf("%s\n", result.get_data()); print_error(result); } diff --git a/modules/database_mysql/mysql_database.cpp b/modules/database_mysql/mysql_database.cpp index 62b1505c9..6916efcb2 100644 --- a/modules/database_mysql/mysql_database.cpp +++ b/modules/database_mysql/mysql_database.cpp @@ -18,7 +18,7 @@ void MysqlDatabase::connect(const String &connection_str) { String dbname = "testappdb"; int port = 3306; - mysql = mysql_real_connect(mysql, host.c_str(), user.c_str(), password.c_str(), dbname.c_str(), port, NULL, 0); + mysql = mysql_real_connect(mysql, host.get_data(), user.get_data(), password.get_data(), dbname.get_data(), port, NULL, 0); if (mysql) { printf("mysql connected\n"); @@ -29,9 +29,9 @@ Ref MysqlDatabase::query(const String &query) { if (!mysql) return nullptr; - //printf("%s\n", query.c_str()); + //printf("%s\n", query.get_data()); - int error = mysql_real_query(mysql, query.c_str(), query.capacity()); + int error = mysql_real_query(mysql, query.get_data(), query.capacity()); if (error) { const char *merr = mysql_error(mysql); @@ -55,9 +55,9 @@ void MysqlDatabase::query_run(const String &query) { if (!mysql) return; - //printf("%s\n", query.c_str()); + //printf("%s\n", query.get_data()); - int error = mysql_real_query(mysql, query.c_str(), query.capacity()); + int error = mysql_real_query(mysql, query.get_data(), query.capacity()); if (error) { const char *merr = mysql_error(mysql); @@ -104,7 +104,7 @@ String MysqlDatabase::escape(const String str) { //You must allocate the to buffer to be at least length*2+1 bytes long. res.ensure_capacity(str.size() * 2 + 1); - mysql_real_escape_string(mysql, res.dataw(), str.c_str(), str.size()); + mysql_real_escape_string(mysql, res.dataw(), str.get_data(), str.size()); return res; } @@ -113,7 +113,7 @@ void MysqlDatabase::escape(const String str, String *to) { //You must allocate the to buffer to be at least length*2+1 bytes long. to->ensure_capacity(str.size() * 2 + 1); - mysql_real_escape_string(mysql, to->dataw(), str.c_str(), str.size()); + mysql_real_escape_string(mysql, to->dataw(), str.get_data(), str.size()); } MysqlDatabase::MysqlDatabase() : diff --git a/modules/database_sqlite/sqlite3_connection.cpp b/modules/database_sqlite/sqlite3_connection.cpp index d134bfa5f..a629904d1 100644 --- a/modules/database_sqlite/sqlite3_connection.cpp +++ b/modules/database_sqlite/sqlite3_connection.cpp @@ -80,7 +80,7 @@ String SQLite3DatabaseConnection::escape(const String &str) { void SQLite3DatabaseConnection::escape_to(const String &str, String *to) { char *ret; - ret = sqlite3_mprintf("%q", str.c_str()); + ret = sqlite3_mprintf("%q", str.utf8().get_data()); if (ret) { to->operator=(ret); diff --git a/modules/regex/regex.cpp b/modules/regex/regex.cpp index 6dd4fa73e..8cb3ca688 100644 --- a/modules/regex/regex.cpp +++ b/modules/regex/regex.cpp @@ -156,26 +156,13 @@ void RegExMatch::_bind_methods() { } void RegEx::_pattern_info(uint32_t what, void *where) const { - if (sizeof(CharType) == 2) { - pcre2_pattern_info_16((pcre2_code_16 *)code, what, where); - - } else { - pcre2_pattern_info_32((pcre2_code_32 *)code, what, where); - } + pcre2_pattern_info_32((pcre2_code_32 *)code, what, where); } void RegEx::clear() { - if (sizeof(CharType) == 2) { - if (code) { - pcre2_code_free_16((pcre2_code_16 *)code); - code = nullptr; - } - - } else { - if (code) { - pcre2_code_free_32((pcre2_code_32 *)code); - code = nullptr; - } + if (code) { + pcre2_code_free_32((pcre2_code_32 *)code); + code = nullptr; } } @@ -187,40 +174,22 @@ Error RegEx::compile(const String &p_pattern) { PCRE2_SIZE offset; uint32_t flags = PCRE2_DUPNAMES; - if (sizeof(CharType) == 2) { - pcre2_general_context_16 *gctx = (pcre2_general_context_16 *)general_ctx; - pcre2_compile_context_16 *cctx = pcre2_compile_context_create_16(gctx); - PCRE2_SPTR16 p = (PCRE2_SPTR16)pattern.c_str(); + pcre2_general_context_32 *gctx = (pcre2_general_context_32 *)general_ctx; + pcre2_compile_context_32 *cctx = pcre2_compile_context_create_32(gctx); + PCRE2_SPTR32 p = (PCRE2_SPTR32)pattern.get_data(); - code = pcre2_compile_16(p, pattern.length(), flags, &err, &offset, cctx); + code = pcre2_compile_32(p, pattern.length(), flags, &err, &offset, cctx); - pcre2_compile_context_free_16(cctx); + pcre2_compile_context_free_32(cctx); - if (!code) { - PCRE2_UCHAR16 buf[256]; - pcre2_get_error_message_16(err, buf, 256); - String message = String::num(offset) + ": " + String((const CharType *)buf); - ERR_PRINT(message.utf8()); - return FAILED; - } - - } else { - pcre2_general_context_32 *gctx = (pcre2_general_context_32 *)general_ctx; - pcre2_compile_context_32 *cctx = pcre2_compile_context_create_32(gctx); - PCRE2_SPTR32 p = (PCRE2_SPTR32)pattern.c_str(); - - code = pcre2_compile_32(p, pattern.length(), flags, &err, &offset, cctx); - - pcre2_compile_context_free_32(cctx); - - if (!code) { - PCRE2_UCHAR32 buf[256]; - pcre2_get_error_message_32(err, buf, 256); - String message = String::num(offset) + ": " + String((const CharType *)buf); - ERR_PRINT(message.utf8()); - return FAILED; - } + if (!code) { + PCRE2_UCHAR32 buf[256]; + pcre2_get_error_message_32(err, buf, 256); + String message = String::num(offset) + ": " + String((const CharType *)buf); + ERR_PRINT(message.utf8()); + return FAILED; } + return OK; } @@ -234,41 +203,11 @@ Ref RegEx::search(const String &p_subject, int p_offset, int p_end) length = p_end; } - if (sizeof(CharType) == 2) { - pcre2_code_16 *c = (pcre2_code_16 *)code; - pcre2_general_context_16 *gctx = (pcre2_general_context_16 *)general_ctx; - pcre2_match_context_16 *mctx = pcre2_match_context_create_16(gctx); - PCRE2_SPTR16 s = (PCRE2_SPTR16)p_subject.c_str(); - - pcre2_match_data_16 *match = pcre2_match_data_create_from_pattern_16(c, gctx); - - int res = pcre2_match_16(c, s, length, p_offset, 0, match, mctx); - - if (res < 0) { - pcre2_match_data_free_16(match); - pcre2_match_context_free_16(mctx); - - return nullptr; - } - - uint32_t size = pcre2_get_ovector_count_16(match); - PCRE2_SIZE *ovector = pcre2_get_ovector_pointer_16(match); - - result->data.resize(size); - - for (uint32_t i = 0; i < size; i++) { - result->data.write[i].start = ovector[i * 2]; - result->data.write[i].end = ovector[i * 2 + 1]; - } - - pcre2_match_data_free_16(match); - pcre2_match_context_free_16(mctx); - - } else { + { pcre2_code_32 *c = (pcre2_code_32 *)code; pcre2_general_context_32 *gctx = (pcre2_general_context_32 *)general_ctx; pcre2_match_context_32 *mctx = pcre2_match_context_create_32(gctx); - PCRE2_SPTR32 s = (PCRE2_SPTR32)p_subject.c_str(); + PCRE2_SPTR32 s = (PCRE2_SPTR32)p_subject.get_data(); pcre2_match_data_32 *match = pcre2_match_data_create_from_pattern_32(c, gctx); @@ -359,37 +298,12 @@ String RegEx::sub(const String &p_subject, const String &p_replacement, bool p_a length = p_end; } - if (sizeof(CharType) == 2) { - pcre2_code_16 *c = (pcre2_code_16 *)code; - pcre2_general_context_16 *gctx = (pcre2_general_context_16 *)general_ctx; - pcre2_match_context_16 *mctx = pcre2_match_context_create_16(gctx); - PCRE2_SPTR16 s = (PCRE2_SPTR16)p_subject.c_str(); - PCRE2_SPTR16 r = (PCRE2_SPTR16)p_replacement.c_str(); - PCRE2_UCHAR16 *o = (PCRE2_UCHAR16 *)output.ptrw(); - - pcre2_match_data_16 *match = pcre2_match_data_create_from_pattern_16(c, gctx); - - int res = pcre2_substitute_16(c, s, length, p_offset, flags, match, mctx, r, p_replacement.length(), o, &olength); - - if (res == PCRE2_ERROR_NOMEMORY) { - output.resize(olength + safety_zone); - o = (PCRE2_UCHAR16 *)output.ptrw(); - res = pcre2_substitute_16(c, s, length, p_offset, flags, match, mctx, r, p_replacement.length(), o, &olength); - } - - pcre2_match_data_free_16(match); - pcre2_match_context_free_16(mctx); - - if (res < 0) { - return String(); - } - - } else { + { pcre2_code_32 *c = (pcre2_code_32 *)code; pcre2_general_context_32 *gctx = (pcre2_general_context_32 *)general_ctx; pcre2_match_context_32 *mctx = pcre2_match_context_create_32(gctx); - PCRE2_SPTR32 s = (PCRE2_SPTR32)p_subject.c_str(); - PCRE2_SPTR32 r = (PCRE2_SPTR32)p_replacement.c_str(); + PCRE2_SPTR32 s = (PCRE2_SPTR32)p_subject.get_data(); + PCRE2_SPTR32 r = (PCRE2_SPTR32)p_replacement.get_data(); PCRE2_UCHAR32 *o = (PCRE2_UCHAR32 *)output.ptrw(); pcre2_match_data_32 *match = pcre2_match_data_create_from_pattern_32(c, gctx); @@ -455,20 +369,14 @@ Array RegEx::get_names() const { } RegEx::RegEx() { - if (sizeof(CharType) == 2) { - general_ctx = pcre2_general_context_create_16(&_regex_malloc, &_regex_free, nullptr); - - } else { + { general_ctx = pcre2_general_context_create_32(&_regex_malloc, &_regex_free, nullptr); } code = nullptr; } RegEx::RegEx(const String &p_pattern) { - if (sizeof(CharType) == 2) { - general_ctx = pcre2_general_context_create_16(&_regex_malloc, &_regex_free, nullptr); - - } else { + { general_ctx = pcre2_general_context_create_32(&_regex_malloc, &_regex_free, nullptr); } code = nullptr; @@ -476,13 +384,7 @@ RegEx::RegEx(const String &p_pattern) { } RegEx::~RegEx() { - if (sizeof(CharType) == 2) { - if (code) { - pcre2_code_free_16((pcre2_code_16 *)code); - } - pcre2_general_context_free_16((pcre2_general_context_16 *)general_ctx); - - } else { + { if (code) { pcre2_code_free_32((pcre2_code_32 *)code); } diff --git a/modules/rtile_map/tile_set.cpp b/modules/rtile_map/tile_set.cpp index 302de7a29..730a736a9 100644 --- a/modules/rtile_map/tile_set.cpp +++ b/modules/rtile_map/tile_set.cpp @@ -38,7 +38,7 @@ bool RTileSet::_set(const StringName &p_name, const Variant &p_value) { if (slash == -1) { return false; } - int id = String::to_int(n.c_str(), slash); + int id = String::to_int(n.get_data(), slash); if (!tile_map.has(id)) { create_tile(id); @@ -214,7 +214,7 @@ bool RTileSet::_get(const StringName &p_name, Variant &r_ret) const { if (slash == -1) { return false; } - int id = String::to_int(n.c_str(), slash); + int id = String::to_int(n.get_data(), slash); ERR_FAIL_COND_V(!tile_map.has(id), false); diff --git a/modules/ui_extensions/input_map_editor.cpp b/modules/ui_extensions/input_map_editor.cpp index 07ca118f6..b18cec1ed 100644 --- a/modules/ui_extensions/input_map_editor.cpp +++ b/modules/ui_extensions/input_map_editor.cpp @@ -91,7 +91,7 @@ void InputMapEditor::_notification(int p_what) { } static bool _validate_action_name(const String &p_name) { - const CharType *cstr = p_name.c_str(); + const CharType *cstr = p_name.get_data(); for (int i = 0; cstr[i]; i++) if (cstr[i] == '/' || cstr[i] == ':' || cstr[i] == '"' || diff --git a/modules/web/nodes/message_page/message_page.cpp b/modules/web/nodes/message_page/message_page.cpp index 0c7ff825f..b1162253f 100644 --- a/modules/web/nodes/message_page/message_page.cpp +++ b/modules/web/nodes/message_page/message_page.cpp @@ -49,13 +49,13 @@ void MessagePage::_migrate(const bool clear, const bool seed_db) { t->drop_table("message_page"); db->query_run(t->result); - printf("%s\n", t->result.c_str()); + //printf("%s\n", t->result.utf8().get_data()); t->result.clear(); t->create_table("message_page")->integer("id")->auto_increment()->primary_key()->next_row()->varchar("text", 30)->ccreate_table(); - printf("%s\n", t->result.c_str()); + //printf("%s\n", t->result.c_str()); db->query(t->result); @@ -63,14 +63,14 @@ void MessagePage::_migrate(const bool clear, const bool seed_db) { b->insert("message_page")->values("default, 'aaewdwd'"); - printf("%s\n", b->query_result.c_str()); + //printf("%s\n", b->query_result.c_str()); db->query_run(b->query_result); b->query_result.clear(); b->insert("message_page")->values("default, 'qqqqq'"); - printf("%s\n", b->query_result.c_str()); + //printf("%s\n", b->query_result.c_str()); db->query_run(b->query_result); } diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp index 92cc0ac35..41c41ac09 100644 --- a/platform/iphone/export/export.cpp +++ b/platform/iphone/export/export.cpp @@ -1040,10 +1040,10 @@ Error EditorExportPlatformIOS::_export_loading_screen_images(const Ref dirs; - String path; String current_dir = p_da->get_current_dir(); p_da->list_dir_begin(); - while ((path = p_da->get_next()).length() != 0) { + String path = p_da->get_next(); + while (!path.empty()) { if (p_da->current_is_dir()) { if (path != "." && path != "..") { dirs.push_back(path); @@ -1055,6 +1055,8 @@ Error EditorExportPlatformIOS::_walk_dir_recursive(DirAccess *p_da, FileHandler return err; } } + + path = p_da->get_next(); } p_da->list_dir_end(); diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm index 56e38cbba..a86f042eb 100644 --- a/platform/iphone/ios.mm +++ b/platform/iphone/ios.mm @@ -173,7 +173,7 @@ String iOS::get_rate_url(int p_app_id) const { String ret = app_url_path.replace("APP_ID", String::num(p_app_id)); - printf("returning rate url %ls\n", ret.c_str()); + printf("returning rate url %s\n", ret.utf8().get_data()); return ret; }; diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm index f05c27053..0428f059a 100644 --- a/platform/iphone/os_iphone.mm +++ b/platform/iphone/os_iphone.mm @@ -88,7 +88,7 @@ void OSIPhone::set_data_dir(String p_dir) { DirAccess *da = DirAccess::open(p_dir); data_dir = da->get_current_dir(); - printf("setting data dir to %ls from %ls\n", data_dir.c_str(), p_dir.c_str()); + printf("setting data dir to %s from %s\n", data_dir.utf8().get_data(), p_dir.utf8().get_data()); memdelete(da); }; diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp index ac88851d1..da1de0fee 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -1298,11 +1298,13 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String DirAccess *da = DirAccess::open(dir); da->list_dir_begin(); - String f; - while ((f = da->get_next()) != "") { + String f = da->get_next(); + while (f.empty()) { if (f == "." || f == "..") { + f = da->get_next(); continue; } + if (da->is_link(f)) { OS::Time time = OS::get_singleton()->get_time(); OS::Date date = OS::get_singleton()->get_date(); @@ -1391,6 +1393,7 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String add_message(EXPORT_MESSAGE_ERROR, TTR("ZIP Creation"), vformat(TTR("Could not open file to read from path \"%s\"."), dir.plus_file(f))); return; } + const int bufsize = 16384; uint8_t buf[bufsize]; @@ -1404,7 +1407,10 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String zipCloseFileInZip(p_zip); } + + f = da->get_next(); } + da->list_dir_end(); memdelete(da); } diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 707adf140..911053a6c 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -114,7 +114,7 @@ static String format_error_message(DWORD id) { size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, NULL); - String msg = "Error " + itos(id) + ": " + String(messageBuffer, size); + String msg = "Error " + itos(id) + ": " + String::utf16((const char16_t *)messageBuffer, size); LocalFree(messageBuffer); @@ -1132,7 +1132,7 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) case WM_DROPFILES: { HDROP hDropInfo = (HDROP)wParam; const int buffsize = 4096; - wchar_t buf[buffsize]; + WCHAR buf[buffsize]; int fcount = DragQueryFileW(hDropInfo, 0xFFFFFFFF, NULL, 0); @@ -1140,7 +1140,7 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) for (int i = 0; i < fcount; i++) { DragQueryFileW(hDropInfo, i, buf, buffsize); - String file = buf; + String file = String::utf16((const char16_t *)buf); files.push_back(file); } @@ -1662,11 +1662,12 @@ void OS_Windows::set_clipboard(const String &p_text) { } EmptyClipboard(); - HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (text.length() + 1) * sizeof(CharType)); + Char16String utf16 = text.utf16(); + HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (utf16.length() + 1) * sizeof(WCHAR)); ERR_FAIL_COND_MSG(mem == NULL, "Unable to allocate memory for clipboard contents."); LPWSTR lptstrCopy = (LPWSTR)GlobalLock(mem); - memcpy(lptstrCopy, text.c_str(), (text.length() + 1) * sizeof(CharType)); + memcpy(lptstrCopy, utf16.get_data(), (utf16.length() + 1) * sizeof(WCHAR)); GlobalUnlock(mem); SetClipboardData(CF_UNICODETEXT, mem); @@ -1697,7 +1698,7 @@ String OS_Windows::get_clipboard() const { if (mem != NULL) { LPWSTR ptr = (LPWSTR)GlobalLock(mem); if (ptr != NULL) { - ret = String((CharType *)ptr); + ret = String::utf16((const char16_t *)ptr); GlobalUnlock(mem); }; }; @@ -1774,7 +1775,7 @@ void OS_Windows::alert(const String &p_alert, const String &p_title) { return; } - MessageBoxW(NULL, p_alert.c_str(), p_title.c_str(), MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL); + MessageBoxW(nullptr, (LPCWSTR)(p_alert.utf16().get_data()), (LPCWSTR)(p_title.utf16().get_data()), MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL); } void OS_Windows::set_mouse_mode(MouseMode p_mode) { @@ -1856,7 +1857,7 @@ int OS_Windows::get_mouse_button_state() const { } void OS_Windows::set_window_title(const String &p_title) { - SetWindowTextW(hWnd, p_title.c_str()); + SetWindowTextW(hWnd, (LPCWSTR)(p_title.utf16().get_data())); } void OS_Windows::set_window_mouse_passthrough(const PoolVector2Array &p_region) { @@ -2341,10 +2342,10 @@ Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_han DLL_DIRECTORY_COOKIE cookie = NULL; if (p_also_set_library_path && has_dll_directory_api) { - cookie = add_dll_directory(path.get_base_dir().c_str()); + cookie = add_dll_directory((LPCWSTR)(path.get_base_dir().utf16().get_data())); } - p_library_handle = (void *)LoadLibraryExW(path.c_str(), NULL, (p_also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0); + p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(path.utf16().get_data()), nullptr, (p_also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0); ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + format_error_message(GetLastError()) + "."); if (cookie) { @@ -2842,11 +2843,7 @@ Error OS_Windows::execute(const String &p_path, const List &p_arguments, ZeroMemory(&pi.pi, sizeof(pi.pi)); LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si; - Vector modstr; // Windows wants to change this no idea why. - modstr.resize(cmdline.size()); - for (int i = 0; i < cmdline.size(); i++) { - modstr.write[i] = cmdline[i]; - } + Char16String modstr = cmdline.utf16(); // Windows wants to change this no idea why. bool inherit_handles = false; HANDLE pipe[2] = { NULL, NULL }; @@ -2874,7 +2871,7 @@ Error OS_Windows::execute(const String &p_path, const List &p_arguments, creaton_flags |= CREATE_NO_WINDOW; } - int ret = CreateProcessW(NULL, modstr.ptrw(), NULL, NULL, inherit_handles, creaton_flags, NULL, NULL, si_w, &pi.pi); + int ret = CreateProcessW(nullptr, (LPWSTR)(modstr.ptrw()), nullptr, nullptr, 0, NORMAL_PRIORITY_CLASS & CREATE_NO_WINDOW, nullptr, nullptr, si_w, &pi.pi); if (!ret && r_pipe) { CloseHandle(pipe[0]); // Cleanup pipe handles. CloseHandle(pipe[1]); @@ -2983,16 +2980,17 @@ bool OS_Windows::is_process_running(const ProcessID &p_pid) const { } Error OS_Windows::set_cwd(const String &p_cwd) { - if (_wchdir(p_cwd.c_str()) != 0) + if (_wchdir((LPCWSTR)(p_cwd.utf16().get_data())) != 0) { return ERR_CANT_OPEN; + } return OK; } String OS_Windows::get_executable_path() const { - wchar_t bufname[4096]; + WCHAR bufname[4096]; GetModuleFileNameW(NULL, bufname, 4096); - String s = bufname; + String s = String::utf16((const char16_t *)bufname); return s.replace("\\", "/"); } @@ -3142,11 +3140,11 @@ void OS_Windows::set_icon(const Ref &p_icon) { bool OS_Windows::has_environment(const String &p_var) const { #ifdef MINGW_ENABLED - return _wgetenv(p_var.c_str()) != NULL; + return _wgetenv((LPCWSTR)(p_var.utf16().get_data())) != nullptr; #else - wchar_t *env; + WCHAR *env; size_t len; - _wdupenv_s(&env, &len, p_var.c_str()); + _wdupenv_s(&env, &len, (LPCWSTR)(p_var.utf16().get_data())); const bool has_env = env != NULL; free(env); return has_env; @@ -3154,16 +3152,16 @@ bool OS_Windows::has_environment(const String &p_var) const { }; String OS_Windows::get_environment(const String &p_var) const { - wchar_t wval[0x7Fff]; // MSDN says 32767 char is the maximum - int wlen = GetEnvironmentVariableW(p_var.c_str(), wval, 0x7Fff); + WCHAR wval[0x7fff]; // MSDN says 32767 char is the maximum + int wlen = GetEnvironmentVariableW((LPCWSTR)(p_var.utf16().get_data()), wval, 0x7fff); if (wlen > 0) { - return wval; + return String::utf16((const char16_t *)wval); } return ""; } bool OS_Windows::set_environment(const String &p_var, const String &p_value) const { - return (bool)SetEnvironmentVariableW(p_var.c_str(), p_value.c_str()); + return (bool)SetEnvironmentVariableW((LPCWSTR)(p_var.utf16().get_data()), (LPCWSTR)(p_value.utf16().get_data())); } String OS_Windows::get_stdin_string(bool p_block) { @@ -3184,7 +3182,8 @@ void OS_Windows::move_window_to_foreground() { } Error OS_Windows::shell_open(String p_uri) { - INT_PTR ret = (INT_PTR)ShellExecuteW(nullptr, nullptr, p_uri.c_str(), nullptr, nullptr, SW_SHOWNORMAL); + INT_PTR ret = (INT_PTR)ShellExecuteW(nullptr, nullptr, (LPCWSTR)(p_uri.utf16().get_data()), nullptr, nullptr, SW_SHOWNORMAL); + if (ret > 32) { return OK; } else { @@ -3265,7 +3264,7 @@ String OS_Windows::get_processor_name() const { const String id = "Hardware\\Description\\System\\CentralProcessor\\0"; HKEY hkey; - if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)(id.c_str()), 0, KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS) { + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)(id.utf16().get_data()), 0, KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS) { ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string.")); } @@ -3385,13 +3384,13 @@ String OS_Windows::keyboard_get_layout_language(int p_index) const { HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL)); GetKeyboardLayoutList(layout_count, layouts); - wchar_t buf[LOCALE_NAME_MAX_LENGTH]; - memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(wchar_t)); + WCHAR buf[LOCALE_NAME_MAX_LENGTH]; + memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(WCHAR)); LCIDToLocaleName(MAKELCID(LOWORD(layouts[p_index]), SORT_DEFAULT), buf, LOCALE_NAME_MAX_LENGTH, 0); memfree(layouts); - return String(buf).substr(0, 2); + return String::utf16((const char16_t *)buf).substr(0, 2); } uint32_t OS_Windows::keyboard_get_scancode_from_physical(uint32_t p_scancode) const { @@ -3435,17 +3434,17 @@ String _get_full_layout_name_from_registry(HKL p_layout) { String ret; HKEY hkey; - wchar_t layout_text[1024]; - memset(layout_text, 0, 1024 * sizeof(wchar_t)); + WCHAR layout_text[1024]; + memset(layout_text, 0, 1024 * sizeof(WCHAR)); - if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)id.c_str(), 0, KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS) { + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)(id.utf16().get_data()), 0, KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS) { return ret; } DWORD buffer = 1024; DWORD vtype = REG_SZ; if (RegQueryValueExW(hkey, L"Layout Text", NULL, &vtype, (LPBYTE)layout_text, &buffer) == ERROR_SUCCESS) { - ret = String(layout_text); + ret = String::utf16((const char16_t *)layout_text); } RegCloseKey(hkey); return ret; @@ -3461,15 +3460,15 @@ String OS_Windows::keyboard_get_layout_name(int p_index) const { String ret = _get_full_layout_name_from_registry(layouts[p_index]); // Try reading full name from Windows registry, fallback to locale name if failed (e.g. on Wine). if (ret == String()) { - wchar_t buf[LOCALE_NAME_MAX_LENGTH]; - memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(wchar_t)); + WCHAR buf[LOCALE_NAME_MAX_LENGTH]; + memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(WCHAR)); LCIDToLocaleName(MAKELCID(LOWORD(layouts[p_index]), SORT_DEFAULT), buf, LOCALE_NAME_MAX_LENGTH, 0); - wchar_t name[1024]; - memset(name, 0, 1024 * sizeof(wchar_t)); + WCHAR name[1024]; + memset(name, 0, 1024 * sizeof(WCHAR)); GetLocaleInfoEx(buf, LOCALE_SLOCALIZEDDISPLAYNAME, (LPWSTR)&name, 1024); - ret = String(name); + ret = String::utf16((const char16_t *)name); } memfree(layouts); @@ -3652,7 +3651,7 @@ String OS_Windows::get_system_dir(SystemDir p_dir, bool p_shared_storage) const PWSTR szPath; HRESULT res = SHGetKnownFolderPath(id, 0, NULL, &szPath); ERR_FAIL_COND_V(res != S_OK, String()); - String path = String(szPath).replace("\\", "/"); + String path = String::utf16((const char16_t *)szPath).replace("\\", "/"); CoTaskMemFree(szPath); return path; } @@ -3678,7 +3677,7 @@ String OS_Windows::get_user_data_dir() const { String OS_Windows::get_unique_id() const { HW_PROFILE_INFO HwProfInfo; ERR_FAIL_COND_V(!GetCurrentHwProfile(&HwProfInfo), ""); - return String(HwProfInfo.szHwProfileGuid); + return String::utf16((const char16_t *)(HwProfInfo.szHwProfileGuid), HW_PROFILE_GUIDLEN); } void OS_Windows::set_ime_active(const bool p_active) { @@ -3759,9 +3758,11 @@ void OS_Windows::process_and_drop_events() { Error OS_Windows::move_to_trash(const String &p_path) { SHFILEOPSTRUCTW sf; - WCHAR *from = new WCHAR[p_path.length() + 2]; - wcscpy_s(from, p_path.length() + 1, p_path.c_str()); - from[p_path.length() + 1] = 0; + + Char16String utf16 = p_path.utf16(); + WCHAR *from = new WCHAR[utf16.length() + 2]; + wcscpy_s(from, utf16.length() + 1, (LPCWSTR)(utf16.get_data())); + from[utf16.length() + 1] = 0; sf.hwnd = hWnd; sf.wFunc = FO_DELETE; diff --git a/platform/windows/windows_terminal_logger.cpp b/platform/windows/windows_terminal_logger.cpp index e83f25b4f..536e002cc 100644 --- a/platform/windows/windows_terminal_logger.cpp +++ b/platform/windows/windows_terminal_logger.cpp @@ -59,10 +59,11 @@ void WindowsTerminalLogger::logv(const char *p_format, va_list p_list, bool p_er MultiByteToWideChar(CP_UTF8, 0, buf, len, wbuf, wlen); wbuf[wlen] = 0; - if (p_err) + if (p_err) { fwprintf(stderr, L"%ls", wbuf); - else + } else { wprintf(L"%ls", wbuf); + } memfree(wbuf); diff --git a/scene/2d/canvas_item.cpp b/scene/2d/canvas_item.cpp index e28edac8e..f209e6d3c 100644 --- a/scene/2d/canvas_item.cpp +++ b/scene/2d/canvas_item.cpp @@ -912,9 +912,9 @@ float CanvasItem::draw_char(const Ref &p_font, const Point2 &p_pos, const ERR_FAIL_COND_V(p_font.is_null(), 0); if (p_font->has_outline()) { - p_font->draw_char(canvas_item, p_pos, p_char[0], p_next.c_str()[0], Color(1, 1, 1), true); + p_font->draw_char(canvas_item, p_pos, p_char[0], p_next.get_data()[0], Color(1, 1, 1), true); } - return p_font->draw_char(canvas_item, p_pos, p_char[0], p_next.c_str()[0], p_modulate); + return p_font->draw_char(canvas_item, p_pos, p_char[0], p_next.get_data()[0], p_modulate); } void CanvasItem::_notify_transform(CanvasItem *p_node) { diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 96677d04f..0dab01204 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -432,10 +432,11 @@ void FileDialog::update_file_list() { List dirs; bool is_hidden; - String item; + String item = dir_access->get_next(); - while ((item = dir_access->get_next()) != "") { + while (!item.empty()) { if (item == "." || item == "..") { + item = dir_access->get_next(); continue; } @@ -448,6 +449,8 @@ void FileDialog::update_file_list() { dirs.push_back(item); } } + + item = dir_access->get_next(); } dirs.sort_custom(); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index e226ecefa..ecef62ed7 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -357,7 +357,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & font = p_base_font; } - const CharType *c = text->text.c_str(); + const CharType *c = text->text.get_data(); const CharType *cf = c; int ascent = font->get_ascent(); int descent = font->get_descent(); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index e81b050f2..866d9ff5e 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -132,7 +132,7 @@ void TextEdit::Text::_update_line_cache(int p_line) const { int w = 0; int len = text[p_line].data.length(); - const CharType *str = text[p_line].data.c_str(); + const CharType *str = text[p_line].data.get_data(); // Update width. @@ -169,7 +169,7 @@ void TextEdit::Text::_update_line_cache(int p_line) const { bool match; if (lr != 0 && lr <= left) { - kc = cr.begin_key.c_str(); + kc = cr.begin_key.get_data(); match = true; @@ -195,7 +195,7 @@ void TextEdit::Text::_update_line_cache(int p_line) const { lr = cr.end_key.length(); if (lr != 0 && lr <= left) { - kc = cr.end_key.c_str(); + kc = cr.end_key.get_data(); match = true; @@ -7368,7 +7368,7 @@ int TextEdit::get_line_width(int p_line, int p_wrap_index) const { int w = 0; int len = rows[p_wrap_index].length(); - const CharType *str = rows[p_wrap_index].c_str(); + const CharType *str = rows[p_wrap_index].get_data(); for (int i = 0; i < len; i++) { w += text.get_char_width(str[i], str[i + 1], w); }