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.
This commit is contained in:
Raul Santos 2022-10-05 18:17:21 +02:00
parent 6178407940
commit d9c495f322
No known key found for this signature in database
GPG key ID: B532473AE3A803E4

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Security; using System.Security;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@ -939,19 +940,94 @@ namespace Godot
return instance.IsSubsequenceOf(text, caseSensitive: false); return instance.IsSubsequenceOf(text, caseSensitive: false);
} }
private static readonly char[] _invalidFileNameCharacters = { ':', '/', '\\', '?', '*', '"', '|', '%', '<', '>' };
/// <summary> /// <summary>
/// Check whether the string contains a valid <see langword="float"/>. /// Returns <see langword="true"/> if this string is free from characters that
/// aren't allowed in file names.
/// </summary> /// </summary>
/// <param name="instance">The string to check.</param> /// <param name="instance">The string to check.</param>
/// <returns>If the string contains a valid file name.</returns>
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;
}
/// <summary>
/// Returns <see langword="true"/> if this string contains a valid <see langword="float"/>.
/// This is inclusive of integers, and also supports exponents.
/// </summary>
/// <example>
/// <code>
/// 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"
/// </code>
/// </example>
/// <param name="instance">The string to check.</param>
/// <returns>If the string contains a valid floating point number.</returns> /// <returns>If the string contains a valid floating point number.</returns>
public static bool IsValidFloat(this string instance) public static bool IsValidFloat(this string instance)
{ {
float f; return float.TryParse(instance, out _);
return float.TryParse(instance, out f);
} }
/// <summary> /// <summary>
/// Check whether the string contains a valid color in HTML notation. /// Returns <see langword="true"/> if this string contains a valid hexadecimal number.
/// If <paramref name="withPrefix"/> is <see langword="true"/>, then a validity of the
/// hexadecimal number is determined by <c>0x</c> prefix, for instance: <c>0xDEADC0DE</c>.
/// </summary>
/// <param name="instance">The string to check.</param>
/// <param name="withPrefix">If the string must contain the <c>0x</c> prefix to be valid.</param>
/// <returns>If the string contains a valid hexadecimal number.</returns>
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');
}
}
/// <summary>
/// Returns <see langword="true"/> if this string contains a valid color in hexadecimal
/// HTML notation. Other HTML notations such as named colors or <c>hsl()</c> aren't
/// considered valid by this method and will return <see langword="false"/>.
/// </summary> /// </summary>
/// <param name="instance">The string to check.</param> /// <param name="instance">The string to check.</param>
/// <returns>If the string contains a valid HTML color.</returns> /// <returns>If the string contains a valid HTML color.</returns>
@ -961,10 +1037,17 @@ namespace Godot
} }
/// <summary> /// <summary>
/// Check whether the string is a valid identifier. As is common in /// Returns <see langword="true"/> if this string is a valid identifier.
/// programming languages, a valid identifier may contain only letters, /// A valid identifier may contain only letters, digits and underscores (<c>_</c>)
/// digits and underscores (_) and the first character may not be a digit. /// and the first character may not be a digit.
/// </summary> /// </summary>
/// <example>
/// <code>
/// GD.Print("good_ident_1".IsValidIdentifier()) // Prints "True"
/// GD.Print("1st_bad_ident".IsValidIdentifier()) // Prints "False"
/// GD.Print("bad_ident_#2".IsValidIdentifier()) // Prints "False"
/// </code>
/// </example>
/// <param name="instance">The string to check.</param> /// <param name="instance">The string to check.</param>
/// <returns>If the string contains a valid identifier.</returns> /// <returns>If the string contains a valid identifier.</returns>
public static bool IsValidIdentifier(this string instance) public static bool IsValidIdentifier(this string instance)
@ -992,25 +1075,59 @@ namespace Godot
} }
/// <summary> /// <summary>
/// Check whether the string contains a valid integer. /// Returns <see langword="true"/> if this string contains a valid <see langword="int"/>.
/// </summary> /// </summary>
/// <example>
/// <code>
/// 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"
/// </code>
/// </example>
/// <param name="instance">The string to check.</param> /// <param name="instance">The string to check.</param>
/// <returns>If the string contains a valid integer.</returns> /// <returns>If the string contains a valid integer.</returns>
public static bool IsValidInteger(this string instance) public static bool IsValidInt(this string instance)
{ {
int f; return int.TryParse(instance, out _);
return int.TryParse(instance, out f);
} }
/// <summary> /// <summary>
/// Check whether the string contains a valid IP address. /// Returns <see langword="true"/> if this string contains only a well-formatted
/// IPv4 or IPv6 address. This method considers reserved IP addresses such as
/// <c>0.0.0.0</c> as valid.
/// </summary> /// </summary>
/// <param name="instance">The string to check.</param> /// <param name="instance">The string to check.</param>
/// <returns>If the string contains a valid IP address.</returns> /// <returns>If the string contains a valid IP address.</returns>
public static bool IsValidIPAddress(this string instance) public static bool IsValidIPAddress(this string instance)
{ {
// TODO: Support IPv6 addresses if (instance.Contains(':'))
string[] ip = instance.Split("."); {
string[] ip = instance.Split(':');
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;
}
if (!n.IsValidIPAddress())
return false;
}
}
else
{
string[] ip = instance.Split('.');
if (ip.Length != 4) if (ip.Length != 4)
return false; return false;
@ -1018,13 +1135,14 @@ namespace Godot
for (int i = 0; i < ip.Length; i++) for (int i = 0; i < ip.Length; i++)
{ {
string n = ip[i]; string n = ip[i];
if (!n.IsValidInteger()) if (!n.IsValidInt())
return false; return false;
int val = n.ToInt(); int val = n.ToInt();
if (val < 0 || val > 255) if (val < 0 || val > 255)
return false; return false;
} }
}
return true; return true;
} }
@ -1745,6 +1863,25 @@ namespace Godot
return Uri.EscapeDataString(instance); return Uri.EscapeDataString(instance);
} }
private const string _uniqueNodePrefix = "%";
private static readonly string[] _invalidNodeNameCharacters = { ".", ":", "@", "/", "\"", _uniqueNodePrefix };
/// <summary>
/// Removes any characters from the string that are prohibited in
/// <see cref="Node"/> names (<c>.</c> <c>:</c> <c>@</c> <c>/</c> <c>"</c>).
/// </summary>
/// <param name="instance">The string to sanitize.</param>
/// <returns>The string sanitized as a valid node name.</returns>
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;
}
/// <summary> /// <summary>
/// Returns a copy of the string with special characters escaped using the XML standard. /// Returns a copy of the string with special characters escaped using the XML standard.
/// </summary> /// </summary>