From 61784079403dd68b95dc304991562bb6505c066b Mon Sep 17 00:00:00 2001 From: Raul Santos Date: Wed, 5 Oct 2022 18:15:39 +0200 Subject: C#: Cleanup and sync StringExtensions with core - Moved `GetBaseName` to keep methods alphabetically sorted. - Removed `Length`, users should just use the Length property. - Removed `Insert`, string already has a method with the same signature that takes precedence. - Removed `Erase`. - Removed `ToLower` and `ToUpper`, string already has methods with the same signature that take precedence. - Removed `FindLast` in favor of `RFind`. - Replaced `RFind` and `RFindN` implemenation with a ca ll to `string.LastIndexOf` to avoid marshaling. - Added `LPad` and `RPad`. - Added `StripEscapes`. - Replaced `LStrip` and `RStrip` implementation with a call to `string.TrimStart` and `string.TrimEnd`. - Added `TrimPrefix` and `TrimSuffix`. - Renamed `OrdAt` to `UnicodeAt`. - Added `CountN` and move the `caseSensitive` parameter of `Count` to the end. - Added `Indent` and `Dedent`. --- .../GodotSharp/Core/NativeInterop/NativeFuncs.cs | 15 - .../GodotSharp/GodotSharp/Core/StringExtensions.cs | 439 +++++++++++++-------- 2 files changed, 275 insertions(+), 179 deletions(-) (limited to 'modules/mono/glue/GodotSharp') diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index b30b6a0752..c7deb6423b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -414,21 +414,6 @@ namespace Godot.NativeInterop // StringExtensions - public static partial void godotsharp_string_md5_buffer(in godot_string p_self, - out godot_packed_byte_array r_md5_buffer); - - public static partial void godotsharp_string_md5_text(in godot_string p_self, out godot_string r_md5_text); - - public static partial int godotsharp_string_rfind(in godot_string p_self, in godot_string p_what, int p_from); - - public static partial int godotsharp_string_rfindn(in godot_string p_self, in godot_string p_what, int p_from); - - public static partial void godotsharp_string_sha256_buffer(in godot_string p_self, - out godot_packed_byte_array r_sha256_buffer); - - public static partial void godotsharp_string_sha256_text(in godot_string p_self, - out godot_string r_sha256_text); - public static partial void godotsharp_string_simplify_path(in godot_string p_self, out godot_string r_simplified_path); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index f511233fcc..4988910fbd 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -66,24 +66,6 @@ namespace Godot return string.Empty; } - /// - /// If the string is a path to a file, return the path to the file without the extension. - /// - /// - /// - /// - /// The path to a file. - /// The path to the file without the extension. - public static string GetBaseName(this string instance) - { - int index = instance.LastIndexOf('.'); - - if (index > 0) - return instance.Substring(0, index); - - return instance; - } - /// /// Returns if the strings begins /// with the given string . @@ -144,15 +126,15 @@ namespace Godot } /// - /// Returns the amount of substrings in the string. + /// Returns the number of occurrences of substring in the string. /// /// The string where the substring will be searched. /// The substring that will be counted. - /// If the search is case sensitive. /// Index to start searching from. /// Index to stop searching at. - /// Amount of substrings in the string. - public static int Count(this string instance, string what, bool caseSensitive = true, int from = 0, int to = 0) + /// If the search is case sensitive. + /// Number of occurrences of the substring in the string. + public static int Count(this string instance, string what, int from = 0, int to = 0, bool caseSensitive = true) { if (what.Length == 0) { @@ -210,6 +192,82 @@ namespace Godot return c; } + /// + /// Returns the number of occurrences of substring (ignoring case) + /// between and positions. If + /// and equals 0 the whole string will be used. If only + /// equals 0 the remained substring will be used. + /// + /// The string where the substring will be searched. + /// The substring that will be counted. + /// Index to start searching from. + /// Index to stop searching at. + /// Number of occurrences of the substring in the string. + public static int CountN(this string instance, string what, int from = 0, int to = 0) + { + return instance.Count(what, from, to, caseSensitive: false); + } + + /// + /// Returns a copy of the string with indentation (leading tabs and spaces) removed. + /// See also to add indentation. + /// + /// The string to remove the indentation from. + /// The string with the indentation removed. + public static string Dedent(this string instance) + { + var sb = new StringBuilder(); + string indent = ""; + bool hasIndent = false; + bool hasText = false; + int lineStart = 0; + int indentStop = -1; + + for (int i = 0; i < instance.Length; i++) + { + char c = instance[i]; + if (c == '\n') + { + if (hasText) + { + sb.Append(instance.Substring(indentStop, i - indentStop)); + } + sb.Append('\n'); + hasText = false; + lineStart = i + 1; + indentStop = -1; + } + else if (!hasText) + { + if (c > 32) + { + hasText = true; + if (!hasIndent) + { + hasIndent = true; + indent = instance.Substring(lineStart, i - lineStart); + indentStop = i; + } + } + if (hasIndent && indentStop < 0) + { + int j = i - lineStart; + if (j >= indent.Length || c != indent[j]) + { + indentStop = i; + } + } + } + } + + if (hasText) + { + sb.Append(instance.Substring(indentStop, instance.Length - indentStop)); + } + + return sb.ToString(); + } + /// /// Returns a copy of the string with special characters escaped using the C language standard. /// @@ -454,17 +512,6 @@ namespace Godot return instance.EndsWith(text); } - /// - /// Erase characters from the string starting from . - /// - /// The string to modify. - /// Starting position from which to erase. - /// Amount of characters to erase. - public static void Erase(this StringBuilder instance, int pos, int chars) - { - instance.Remove(pos, chars); - } - /// /// Returns the extension without the leading period character (.) /// if the string is a valid file name or path. If the string does not contain @@ -489,7 +536,7 @@ namespace Godot /// The extension of the file or an empty string. public static string GetExtension(this string instance) { - int pos = instance.FindLast("."); + int pos = instance.RFind("."); if (pos < 0) return instance; @@ -498,12 +545,16 @@ namespace Godot } /// - /// Find the first occurrence of a substring. Optionally, the search starting position can be passed. + /// Returns the index of the first occurrence of the specified string in this instance, + /// or -1. Optionally, the starting search index can be specified, continuing + /// to the end of the string. + /// Note: If you just want to know whether a string contains a substring, use the + /// method. /// /// - /// - /// /// + /// + /// /// The string that will be searched. /// The substring to find. /// The search starting position. @@ -519,9 +570,9 @@ namespace Godot /// Find the first occurrence of a char. Optionally, the search starting position can be passed. /// /// - /// - /// /// + /// + /// /// The string that will be searched. /// The substring to find. /// The search starting position. @@ -529,50 +580,21 @@ namespace Godot /// The first instance of the char, or -1 if not found. public static int Find(this string instance, char what, int from = 0, bool caseSensitive = true) { - // TODO: Could be more efficient if we get a char version of `IndexOf`. - // See https://github.com/dotnet/runtime/issues/44116 - return instance.IndexOf(what.ToString(), from, - caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); - } - - /// Find the last occurrence of a substring. - /// - /// - /// - /// - /// The string that will be searched. - /// The substring to find. - /// If , the search is case sensitive. - /// The starting position of the substring, or -1 if not found. - public static int FindLast(this string instance, string what, bool caseSensitive = true) - { - return instance.FindLast(what, instance.Length - 1, caseSensitive); - } + if (caseSensitive) + return instance.IndexOf(what, from); - /// Find the last occurrence of a substring specifying the search starting position. - /// - /// - /// - /// - /// The string that will be searched. - /// The substring to find. - /// The search starting position. - /// If , the search is case sensitive. - /// The starting position of the substring, or -1 if not found. - public static int FindLast(this string instance, string what, int from, bool caseSensitive = true) - { - return instance.LastIndexOf(what, from, - caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + return CultureInfo.InvariantCulture.CompareInfo.IndexOf(instance, what, from, CompareOptions.OrdinalIgnoreCase); } /// - /// Find the first occurrence of a substring but search as case-insensitive. - /// Optionally, the search starting position can be passed. + /// Returns the index of the first case-insensitive occurrence of the specified string in this instance, + /// or -1. Optionally, the starting search index can be specified, continuing + /// to the end of the string. /// /// /// - /// - /// + /// + /// /// The string that will be searched. /// The substring to find. /// The search starting position. @@ -616,7 +638,7 @@ namespace Godot } } - int sep = Mathf.Max(rs.FindLast("/"), rs.FindLast("\\")); + int sep = Mathf.Max(rs.RFind("/"), rs.RFind("\\")); if (sep == -1) return directory; @@ -624,6 +646,24 @@ namespace Godot return directory + rs.Substr(0, sep); } + /// + /// If the string is a path to a file, return the path to the file without the extension. + /// + /// + /// + /// + /// The path to a file. + /// The path to the file without the extension. + public static string GetBaseName(this string instance) + { + int index = instance.RFind("."); + + if (index > 0) + return instance.Substring(0, index); + + return instance; + } + /// /// If the string is a path to a file, return the file and ignore the base directory. /// @@ -634,7 +674,7 @@ namespace Godot /// The file name. public static string GetFile(this string instance) { - int sep = Mathf.Max(instance.FindLast("/"), instance.FindLast("\\")); + int sep = Mathf.Max(instance.RFind("/"), instance.RFind("\\")); if (sep == -1) return instance; @@ -766,18 +806,44 @@ namespace Godot } /// - /// Inserts a substring at a given position. + /// Returns a copy of the string with lines indented with . + /// For example, the string can be indented with two tabs using "\t\t", + /// or four spaces using " ". The prefix can be any string so it can + /// also be used to comment out strings with e.g. "// . + /// See also to remove indentation. + /// Note: Empty lines are kept empty. /// - /// The string to modify. - /// Position at which to insert the substring. - /// Substring to insert. - /// - /// The string with inserted at the given - /// position . - /// - public static string Insert(this string instance, int pos, string what) + /// The string to add indentation to. + /// The string to use as indentation. + /// The string with indentation added. + public static string Indent(this string instance, string prefix) { - return instance.Insert(pos, what); + var sb = new StringBuilder(); + int lineStart = 0; + + for (int i = 0; i < instance.Length; i++) + { + char c = instance[i]; + if (c == '\n') + { + if (i == lineStart) + { + sb.Append(c); // Leave empty lines empty. + } + else + { + sb.Append(prefix); + sb.Append(instance.Substring(lineStart, i - lineStart + 1)); + } + lineStart = i + 1; + } + } + if (lineStart != instance.Length) + { + sb.Append(prefix); + sb.Append(instance.Substring(lineStart)); + } + return sb.ToString(); } /// @@ -1003,17 +1069,24 @@ namespace Godot } /// - /// Returns the length of the string in characters. + /// Formats a string to be at least long by + /// adding s to the left of the string. /// - /// The string to check. - /// The length of the string. - public static int Length(this string instance) + /// String that will be padded. + /// Minimum length that the resulting string must have. + /// Character to add to the left of the string. + /// String padded with the specified character. + public static string LPad(this string instance, int minLength, char character = ' ') { - return instance.Length; + return instance.PadLeft(minLength, character); } /// /// Returns a copy of the string with characters removed from the left. + /// The argument is a string specifying the set of characters + /// to be removed. + /// Note: The is not a prefix. See + /// method that will remove a single prefix string rather than a set of characters. /// /// /// The string to remove characters from. @@ -1021,23 +1094,7 @@ namespace Godot /// A copy of the string with characters removed from the left. public static string LStrip(this string instance, string chars) { - int len = instance.Length; - int beg; - - for (beg = 0; beg < len; beg++) - { - if (chars.Find(instance[beg]) == -1) - { - break; - } - } - - if (beg == 0) - { - return instance; - } - - return instance.Substr(beg, len - beg); + return instance.TrimStart(chars.ToCharArray()); } /// @@ -1150,17 +1207,6 @@ namespace Godot return instance.CompareTo(to, caseSensitive: false); } - /// - /// Returns the character code at position . - /// - /// The string to check. - /// The position int the string for the character to check. - /// The character code. - public static int OrdAt(this string instance, int at) - { - return instance[at]; - } - /// /// Format a number to have an exact number of /// after the decimal point. @@ -1282,34 +1328,47 @@ namespace Godot } /// - /// Perform a search for a substring, but start from the end of the string instead of the beginning. + /// Returns the index of the last occurrence of the specified string in this instance, + /// or -1. Optionally, the starting search index can be specified, continuing to + /// the beginning of the string. /// + /// + /// + /// /// /// The string that will be searched. /// The substring to search in the string. /// The position at which to start searching. + /// If , the search is case sensitive. /// The position at which the substring was found, or -1 if not found. - public static int RFind(this string instance, string what, int from = -1) + public static int RFind(this string instance, string what, int from = -1, bool caseSensitive = true) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - using godot_string whatStr = Marshaling.ConvertStringToNative(instance); - return NativeFuncs.godotsharp_string_rfind(instanceStr, whatStr, from); + if (from == -1) + from = instance.Length - 1; + + return instance.LastIndexOf(what, from, + caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); } /// - /// Perform a search for a substring, but start from the end of the string instead of the beginning. - /// Also search case-insensitive. + /// Returns the index of the last case-insensitive occurrence of the specified string in this instance, + /// or -1. Optionally, the starting search index can be specified, continuing to + /// the beginning of the string. /// - /// + /// + /// + /// + /// /// The string that will be searched. /// The substring to search in the string. /// The position at which to start searching. /// The position at which the substring was found, or -1 if not found. public static int RFindN(this string instance, string what, int from = -1) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - using godot_string whatStr = Marshaling.ConvertStringToNative(instance); - return NativeFuncs.godotsharp_string_rfindn(instanceStr, whatStr, from); + if (from == -1) + from = instance.Length - 1; + + return instance.LastIndexOf(what, from, StringComparison.OrdinalIgnoreCase); } /// @@ -1330,8 +1389,25 @@ namespace Godot return instance.Substring(pos, instance.Length - pos); } + /// + /// Formats a string to be at least long by + /// adding s to the right of the string. + /// + /// String that will be padded. + /// Minimum length that the resulting string must have. + /// Character to add to the right of the string. + /// String padded with the specified character. + public static string RPad(this string instance, int minLength, char character = ' ') + { + return instance.PadRight(minLength, character); + } + /// /// Returns a copy of the string with characters removed from the right. + /// The argument is a string specifying the set of characters + /// to be removed. + /// Note: The is not a suffix. See + /// method that will remove a single suffix string rather than a set of characters. /// /// /// The string to remove characters from. @@ -1339,16 +1415,8 @@ namespace Godot /// A copy of the string with characters removed from the right. public static string RStrip(this string instance, string chars) { - int len = instance.Length; - int end; - - for (end = len - 1; end >= 0; end--) - { - if (chars.Find(instance[end]) == -1) - { - break; - } - } + return instance.TrimEnd(chars.ToCharArray()); + } if (end == len - 1) { @@ -1455,7 +1523,7 @@ namespace Godot /// The array of strings split from the string. public static string[] Split(this string instance, string divisor, bool allowEmpty = true) { - return instance.Split(new[] { divisor }, + return instance.Split(divisor, allowEmpty ? StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries); } @@ -1503,8 +1571,10 @@ namespace Godot }; /// - /// Returns a copy of the string stripped of any non-printable character at the beginning and the end. - /// The optional arguments are used to toggle stripping on the left and right edges respectively. + /// Returns a copy of the string stripped of any non-printable character + /// (including tabulations, spaces and line breaks) at the beginning and the end. + /// The optional arguments are used to toggle stripping on the left and right + /// edges respectively. /// /// The string to strip. /// If the left side should be stripped. @@ -1522,6 +1592,30 @@ namespace Godot return instance.TrimEnd(_nonPrintable); } + + /// + /// Returns a copy of the string stripped of any escape character. + /// These include all non-printable control characters of the first page + /// of the ASCII table (< 32), such as tabulation (\t) and + /// newline (\n and \r) characters, but not spaces. + /// + /// The string to strip. + /// The string stripped of any escape characters. + public static string StripEscapes(this string instance) + { + var sb = new StringBuilder(); + for (int i = 0; i < instance.Length; i++) + { + // Escape characters on first page of the ASCII table, before 32 (Space). + if (instance[i] < 32) + continue; + + sb.Append(instance[i]); + } + + return sb.ToString(); + } + /// /// Returns part of the string from the position , with length . /// @@ -1573,38 +1667,55 @@ namespace Godot } /// - /// Returns the string converted to lowercase. + /// Converts the String (which is an array of characters) to PackedByteArray (which is an array of bytes). + /// The conversion is a bit slower than , but supports all UTF-8 characters. + /// Therefore, you should prefer this function over . /// - /// + /// /// The string to convert. - /// The string converted to lowercase. - public static string ToLower(this string instance) + /// The string as UTF-8 encoded bytes. + public static byte[] ToUTF8(this string instance) { - return instance.ToLower(); + return Encoding.UTF8.GetBytes(instance); } /// - /// Returns the string converted to uppercase. + /// Removes a given string from the start if it starts with it or leaves the string unchanged. /// - /// - /// The string to convert. - /// The string converted to uppercase. - public static string ToUpper(this string instance) + /// The string to remove the prefix from. + /// The string to remove from the start. + /// A copy of the string with the prefix string removed from the start. + public static string TrimPrefix(this string instance, string prefix) { - return instance.ToUpper(); + if (instance.StartsWith(prefix)) + return instance.Substring(prefix.Length); + + return instance; } /// - /// Converts the String (which is an array of characters) to PackedByteArray (which is an array of bytes). - /// The conversion is a bit slower than , but supports all UTF-8 characters. - /// Therefore, you should prefer this function over . + /// Removes a given string from the end if it ends with it or leaves the string unchanged. /// - /// - /// The string to convert. - /// The string as UTF-8 encoded bytes. - public static byte[] ToUTF8(this string instance) + /// The string to remove the suffix from. + /// The string to remove from the end. + /// A copy of the string with the suffix string removed from the end. + public static string TrimSuffix(this string instance, string suffix) { - return Encoding.UTF8.GetBytes(instance); + if (instance.EndsWith(suffix)) + return instance.Substring(0, instance.Length - suffix.Length); + + return instance; + } + + /// + /// Returns the character code at position . + /// + /// The string to check. + /// The position int the string for the character to check. + /// The character code. + public static int UnicodeAt(this string instance, int at) + { + return instance[at]; } /// -- cgit v1.2.3 From d9c495f322e36962aabe3fbe1075409adc49913d Mon Sep 17 00:00:00 2001 From: Raul Santos Date: Wed, 5 Oct 2022 18:17:21 +0200 Subject: C#: Cleanup and sync `IsValid*` StringExtensions with core - Renamed `IsValidInteger` to `IsValidInt`. - Added `IsValidFileName`. - Added `IsValidHexNumber`. - Added support for IPv6 to `IsValidIPAddress`. - Added `ValidateNodeName`. - Updated the documentation of the `IsValid*` methods. --- .../GodotSharp/GodotSharp/Core/StringExtensions.cs | 181 ++++++++++++++++++--- 1 file changed, 159 insertions(+), 22 deletions(-) (limited to 'modules/mono/glue/GodotSharp') diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index 4988910fbd..92ea24f90b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Security; using System.Text; using System.Text.RegularExpressions; @@ -939,19 +940,94 @@ namespace Godot return instance.IsSubsequenceOf(text, caseSensitive: false); } + private static readonly char[] _invalidFileNameCharacters = { ':', '/', '\\', '?', '*', '"', '|', '%', '<', '>' }; + + /// + /// Returns if this string is free from characters that + /// aren't allowed in file names. + /// + /// The string to check. + /// If the string contains a valid file name. + public static bool IsValidFileName(this string instance) + { + var stripped = instance.Trim(); + if (instance != stripped) + return false; + + if (string.IsNullOrEmpty(stripped)) + return false; + + return instance.IndexOfAny(_invalidFileNameCharacters) == -1; + } + /// - /// Check whether the string contains a valid . + /// Returns if this string contains a valid . + /// This is inclusive of integers, and also supports exponents. /// + /// + /// + /// GD.Print("1.7".IsValidFloat()) // Prints "True" + /// GD.Print("24".IsValidFloat()) // Prints "True" + /// GD.Print("7e3".IsValidFloat()) // Prints "True" + /// GD.Print("Hello".IsValidFloat()) // Prints "False" + /// + /// /// The string to check. /// If the string contains a valid floating point number. public static bool IsValidFloat(this string instance) { - float f; - return float.TryParse(instance, out f); + return float.TryParse(instance, out _); + } + + /// + /// Returns if this string contains a valid hexadecimal number. + /// If is , then a validity of the + /// hexadecimal number is determined by 0x prefix, for instance: 0xDEADC0DE. + /// + /// The string to check. + /// If the string must contain the 0x prefix to be valid. + /// If the string contains a valid hexadecimal number. + public static bool IsValidHexNumber(this string instance, bool withPrefix = false) + { + if (string.IsNullOrEmpty(instance)) + return false; + + int from = 0; + if (instance.Length != 1 && instance[0] == '+' || instance[0] == '-') + { + from++; + } + + if (withPrefix) + { + if (instance.Length < 3) + return false; + if (instance[from] != '0' || instance[from + 1] != 'x') + return false; + from += 2; + } + + for (int i = from; i < instance.Length; i++) + { + char c = instance[i]; + if (IsHexDigit(c)) + continue; + + return false; + } + + return true; + + static bool IsHexDigit(char c) + { + return char.IsDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); + } } /// - /// Check whether the string contains a valid color in HTML notation. + /// Returns if this string contains a valid color in hexadecimal + /// HTML notation. Other HTML notations such as named colors or hsl() aren't + /// considered valid by this method and will return . /// /// The string to check. /// If the string contains a valid HTML color. @@ -961,10 +1037,17 @@ namespace Godot } /// - /// Check whether the string is a valid identifier. As is common in - /// programming languages, a valid identifier may contain only letters, - /// digits and underscores (_) and the first character may not be a digit. + /// Returns if this string is a valid identifier. + /// A valid identifier may contain only letters, digits and underscores (_) + /// and the first character may not be a digit. /// + /// + /// + /// GD.Print("good_ident_1".IsValidIdentifier()) // Prints "True" + /// GD.Print("1st_bad_ident".IsValidIdentifier()) // Prints "False" + /// GD.Print("bad_ident_#2".IsValidIdentifier()) // Prints "False" + /// + /// /// The string to check. /// If the string contains a valid identifier. public static bool IsValidIdentifier(this string instance) @@ -992,38 +1075,73 @@ namespace Godot } /// - /// Check whether the string contains a valid integer. + /// Returns if this string contains a valid . /// + /// + /// + /// GD.Print("7".IsValidInt()) // Prints "True" + /// GD.Print("14.6".IsValidInt()) // Prints "False" + /// GD.Print("L".IsValidInt()) // Prints "False" + /// GD.Print("+3".IsValidInt()) // Prints "True" + /// GD.Print("-12".IsValidInt()) // Prints "True" + /// + /// /// The string to check. /// If the string contains a valid integer. - public static bool IsValidInteger(this string instance) + public static bool IsValidInt(this string instance) { - int f; - return int.TryParse(instance, out f); + return int.TryParse(instance, out _); } /// - /// Check whether the string contains a valid IP address. + /// Returns if this string contains only a well-formatted + /// IPv4 or IPv6 address. This method considers reserved IP addresses such as + /// 0.0.0.0 as valid. /// /// The string to check. /// If the string contains a valid IP address. public static bool IsValidIPAddress(this string instance) { - // TODO: Support IPv6 addresses - string[] ip = instance.Split("."); + if (instance.Contains(':')) + { + string[] ip = instance.Split(':'); - if (ip.Length != 4) - return false; + for (int i = 0; i < ip.Length; i++) + { + string n = ip[i]; + if (n.Length == 0) + continue; + + if (n.IsValidHexNumber(withPrefix: false)) + { + long nint = n.HexToInt(); + if (nint < 0 || nint > 0xffff) + return false; + + continue; + } - for (int i = 0; i < ip.Length; i++) + if (!n.IsValidIPAddress()) + return false; + } + } + else { - string n = ip[i]; - if (!n.IsValidInteger()) - return false; + string[] ip = instance.Split('.'); - int val = n.ToInt(); - if (val < 0 || val > 255) + if (ip.Length != 4) return false; + + for (int i = 0; i < ip.Length; i++) + { + string n = ip[i]; + if (!n.IsValidInt()) + return false; + + int val = n.ToInt(); + if (val < 0 || val > 255) + return false; + } } return true; @@ -1745,6 +1863,25 @@ namespace Godot return Uri.EscapeDataString(instance); } + private const string _uniqueNodePrefix = "%"; + private static readonly string[] _invalidNodeNameCharacters = { ".", ":", "@", "/", "\"", _uniqueNodePrefix }; + + /// + /// Removes any characters from the string that are prohibited in + /// names (. : @ / "). + /// + /// The string to sanitize. + /// The string sanitized as a valid node name. + public static string ValidateNodeName(this string instance) + { + string name = instance.Replace(_invalidNodeNameCharacters[0], ""); + for (int i = 1; i < _invalidNodeNameCharacters.Length; i++) + { + name = name.Replace(_invalidNodeNameCharacters[i], ""); + } + return name; + } + /// /// Returns a copy of the string with special characters escaped using the XML standard. /// -- cgit v1.2.3 From d0b166d8e4e326e84e7e43ac1ed5bc292be1d2fa Mon Sep 17 00:00:00 2001 From: Raul Santos Date: Wed, 5 Oct 2022 18:22:06 +0200 Subject: C#: Cleanup and sync crypto/buffer StringExtensions with core - Replaced `MD5Buffer`, `MD5Text`, `SHA256Buffer` and `SHA256Text` implementation to use the `System.Security.Cryptography` classes and avoid marshaling. - Added `SHA1Buffer` and `SHA1Text`. - Renamed `ToUTF8` to `ToUTF8Buffer`. - Renamed `ToAscii` to `ToASCIIBuffer`. - Added `ToUTF16Buffer` and `ToUTF32Buffer`. - Added `GetStringFromUTF16` and `GetStringFromUTF32`. --- .../GodotSharp/GodotSharp/Core/StringExtensions.cs | 128 +++++++++++++++------ 1 file changed, 94 insertions(+), 34 deletions(-) (limited to 'modules/mono/glue/GodotSharp') diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index 92ea24f90b..dd399fb24b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Security; +using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using Godot.NativeInterop; @@ -684,8 +685,8 @@ namespace Godot } /// - /// Converts the given byte array of ASCII encoded text to a string. - /// Faster alternative to if the + /// Converts ASCII encoded array to string. + /// Fast alternative to 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 @@ -699,13 +700,35 @@ namespace Godot } /// - /// Converts the given byte array of UTF-8 encoded text to a string. + /// Converts UTF-16 encoded array to string using the little endian byte order. + /// + /// A byte array of UTF-16 characters. + /// A string created from the bytes. + public static string GetStringFromUTF16(this byte[] bytes) + { + return Encoding.Unicode.GetString(bytes); + } + + /// + /// Converts UTF-32 encoded array to string using the little endian byte order. + /// + /// A byte array of UTF-32 characters. + /// A string created from the bytes. + public static string GetStringFromUTF32(this byte[] bytes) + { + return Encoding.UTF32.GetString(bytes); + } + + /// + /// Converts UTF-8 encoded array to string. /// Slower than 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. /// - /// A byte array of UTF-8 characters (a character may take up multiple bytes). + /// + /// A byte array of UTF-8 characters (a character may take up multiple bytes). + /// /// A string created from the bytes. public static string GetStringFromUTF8(this byte[] bytes) { @@ -1292,10 +1315,9 @@ namespace Godot /// The MD5 hash of the string. public static byte[] MD5Buffer(this string instance) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - NativeFuncs.godotsharp_string_md5_buffer(instanceStr, out var md5Buffer); - using (md5Buffer) - return Marshaling.ConvertNativePackedByteArrayToSystemArray(md5Buffer); +#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms + return MD5.HashData(Encoding.UTF8.GetBytes(instance)); +#pragma warning restore CA5351 } /// @@ -1306,10 +1328,7 @@ namespace Godot /// The MD5 hash of the string. public static string MD5Text(this string instance) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - NativeFuncs.godotsharp_string_md5_text(instanceStr, out var md5Text); - using (md5Text) - return Marshaling.ConvertStringToManaged(md5Text); + return instance.MD5Buffer().HexEncode(); } /// @@ -1536,12 +1555,28 @@ namespace Godot return instance.TrimEnd(chars.ToCharArray()); } - if (end == len - 1) - { - return instance; - } + /// + /// Returns the SHA-1 hash of the string as an array of bytes. + /// + /// + /// The string to hash. + /// The SHA-1 hash of the string. + public static byte[] SHA1Buffer(this string instance) + { +#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms + return SHA1.HashData(Encoding.UTF8.GetBytes(instance)); +#pragma warning restore CA5350 + } - return instance.Substr(0, end + 1); + /// + /// Returns the SHA-1 hash of the string as a string. + /// + /// + /// The string to hash. + /// The SHA-1 hash of the string. + public static string SHA1Text(this string instance) + { + return instance.SHA1Buffer().HexEncode(); } /// @@ -1552,10 +1587,7 @@ namespace Godot /// The SHA-256 hash of the string. public static byte[] SHA256Buffer(this string instance) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - NativeFuncs.godotsharp_string_sha256_buffer(instanceStr, out var sha256Buffer); - using (sha256Buffer) - return Marshaling.ConvertNativePackedByteArrayToSystemArray(sha256Buffer); + return SHA256.HashData(Encoding.UTF8.GetBytes(instance)); } /// @@ -1566,10 +1598,7 @@ namespace Godot /// The SHA-256 hash of the string. public static string SHA256Text(this string instance) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - NativeFuncs.godotsharp_string_sha256_text(instanceStr, out var sha256Text); - using (sha256Text) - return Marshaling.ConvertStringToManaged(sha256Text); + return instance.SHA256Buffer().HexEncode(); } /// @@ -1751,13 +1780,15 @@ namespace Godot /// /// Converts the String (which is a character array) to PackedByteArray (which is an array of bytes). - /// The conversion is speeded up in comparison to with the assumption - /// that all the characters the String contains are only ASCII characters. + /// The conversion is faster compared to , + /// as this method assumes that all the characters in the String are ASCII characters. /// - /// + /// + /// + /// /// The string to convert. /// The string as ASCII encoded bytes. - public static byte[] ToAscii(this string instance) + public static byte[] ToASCIIBuffer(this string instance) { return Encoding.ASCII.GetBytes(instance); } @@ -1785,14 +1816,43 @@ namespace Godot } /// - /// Converts the String (which is an array of characters) to PackedByteArray (which is an array of bytes). - /// The conversion is a bit slower than , but supports all UTF-8 characters. - /// Therefore, you should prefer this function over . + /// Converts the string (which is an array of characters) to an UTF-16 encoded array of bytes. + /// + /// + /// + /// + /// The string to convert. + /// The string as UTF-16 encoded bytes. + public static byte[] ToUTF16Buffer(this string instance) + { + return Encoding.Unicode.GetBytes(instance); + } + + /// + /// Converts the string (which is an array of characters) to an UTF-32 encoded array of bytes. + /// + /// + /// + /// + /// The string to convert. + /// The string as UTF-32 encoded bytes. + public static byte[] ToUTF32Buffer(this string instance) + { + return Encoding.UTF32.GetBytes(instance); + } + + /// + /// Converts the string (which is an array of characters) to an UTF-8 encoded array of bytes. + /// The conversion is a bit slower than , + /// but supports all UTF-8 characters. Therefore, you should prefer this function + /// over . /// - /// + /// + /// + /// /// The string to convert. /// The string as UTF-8 encoded bytes. - public static byte[] ToUTF8(this string instance) + public static byte[] ToUTF8Buffer(this string instance) { return Encoding.UTF8.GetBytes(instance); } -- cgit v1.2.3 From dc2ceef0ecb35210dc9419dbb4d0e22c4fe1cad7 Mon Sep 17 00:00:00 2001 From: Raul Santos Date: Sun, 27 Nov 2022 23:25:48 +0100 Subject: C#: Remove/deprecate unnecessary string extensions - Removed `UnicodeAt` - Removed `EndsWith` - Removed `LPad` and `RPad` - Deprecated `BeginsWith` in favor of `string.StartsWith` - Deprecated `LStrip` and `RStrip` in favor of `string.TrimStart` and `string.TrimEnd` --- .../GodotSharp/GodotSharp/Core/StringExtensions.cs | 52 ++-------------------- 1 file changed, 3 insertions(+), 49 deletions(-) (limited to 'modules/mono/glue/GodotSharp') diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index dd399fb24b..d4329d78c1 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -75,6 +75,7 @@ namespace Godot /// The string to check. /// The beginning string. /// If the string begins with the given string. + [Obsolete("Use string.StartsWith instead.")] public static bool BeginsWith(this string instance, string text) { return instance.StartsWith(text); @@ -502,18 +503,6 @@ namespace Godot } } - /// - /// Returns if the strings ends - /// with the given string . - /// - /// The string to check. - /// The ending string. - /// If the string ends with the given string. - public static bool EndsWith(this string instance, string text) - { - return instance.EndsWith(text); - } - /// /// Returns the extension without the leading period character (.) /// if the string is a valid file name or path. If the string does not contain @@ -1209,19 +1198,6 @@ namespace Godot return instance.Substring(0, pos); } - /// - /// Formats a string to be at least long by - /// adding s to the left of the string. - /// - /// String that will be padded. - /// Minimum length that the resulting string must have. - /// Character to add to the left of the string. - /// String padded with the specified character. - public static string LPad(this string instance, int minLength, char character = ' ') - { - return instance.PadLeft(minLength, character); - } - /// /// Returns a copy of the string with characters removed from the left. /// The argument is a string specifying the set of characters @@ -1233,6 +1209,7 @@ namespace Godot /// The string to remove characters from. /// The characters to be removed. /// A copy of the string with characters removed from the left. + [Obsolete("Use string.TrimStart instead.")] public static string LStrip(this string instance, string chars) { return instance.TrimStart(chars.ToCharArray()); @@ -1526,19 +1503,6 @@ namespace Godot return instance.Substring(pos, instance.Length - pos); } - /// - /// Formats a string to be at least long by - /// adding s to the right of the string. - /// - /// String that will be padded. - /// Minimum length that the resulting string must have. - /// Character to add to the right of the string. - /// String padded with the specified character. - public static string RPad(this string instance, int minLength, char character = ' ') - { - return instance.PadRight(minLength, character); - } - /// /// Returns a copy of the string with characters removed from the right. /// The argument is a string specifying the set of characters @@ -1550,6 +1514,7 @@ namespace Godot /// The string to remove characters from. /// The characters to be removed. /// A copy of the string with characters removed from the right. + [Obsolete("Use string.TrimEnd instead.")] public static string RStrip(this string instance, string chars) { return instance.TrimEnd(chars.ToCharArray()); @@ -1885,17 +1850,6 @@ namespace Godot return instance; } - /// - /// Returns the character code at position . - /// - /// The string to check. - /// The position int the string for the character to check. - /// The character code. - public static int UnicodeAt(this string instance, int at) - { - return instance[at]; - } - /// /// Decodes a string in URL encoded format. This is meant to /// decode parameters in a URL when receiving an HTTP request. -- cgit v1.2.3