using System; using System.Linq; using System.Text; namespace BizHawk.Common.StringExtensions { /// TODO how many of these Is*/Only* methods' callers can use int.TryParse or similar instead? --yoshi public static class StringExtensions { #pragma warning disable CS8602 // no idea --yoshi /// the substring of before the first occurrence of , or if not found public static string? GetPrecedingString(this string str, string value) { var index = str.IndexOf(value); return index < 0 ? null : str.Substring(0, index); } /// iff appears in (case-insensitive) public static bool In(this string str, params string[] options) => options.Any(opt => string.Equals(opt, str, StringComparison.InvariantCultureIgnoreCase)); /// how many times appears in , or 0 if is null public static int HowMany(this string? str, char c) => string.IsNullOrEmpty(str) ? 0 : str.Count(t => t == c); /// how many times appears in , or 0 if is null /// /// occurrences may overlap, for example "AAA".HowMany("AA") returns 2
/// TODO except it doesn't, but "AAAB".HowMany("AA") does. I left this bug in so as to not break anything. --yoshi ///
public static int HowMany(this string? str, string sub) { if (string.IsNullOrEmpty(str)) return 0; var count = 0; var substrLength = sub.Length; for (int i = 0, l = str.Length - substrLength; i < l; i++) { if (string.Equals(str.Substring(i, substrLength), sub, StringComparison.InvariantCulture)) count++; } return count; } /// iff is not and all chars of are digits public static bool IsUnsigned(this string? str) => !string.IsNullOrWhiteSpace(str) && str.All(IsUnsigned); /// iff is a digit public static bool IsUnsigned(this char c) => char.IsDigit(c); /// /// iff is not , /// the first char of is '-' or a digit, and /// all subsequent chars of are digits /// public static bool IsSigned(this string? str) => !string.IsNullOrWhiteSpace(str) && str[0].IsSigned() && str.Substring(1).All(IsUnsigned); /// iff is '-' or a digit public static bool IsSigned(this char c) => IsUnsigned(c) || c == '-'; /// iff is not and all chars of are hex digits ([0-9A-Fa-f]) /// should exclude the prefix 0x public static bool IsHex(this string? str) => !string.IsNullOrWhiteSpace(str) && str.All(IsHex); /// iff is a hex digit ([0-9A-Fa-f]) public static bool IsHex(this char c) => IsUnsigned(c) || 'A' <= char.ToUpperInvariant(c) && char.ToUpperInvariant(c) <= 'F'; /// iff is not and all chars of are either '0' or '1' /// should exclude the prefix 0b public static bool IsBinary(this string? str) => !string.IsNullOrWhiteSpace(str) && str.All(IsBinary); /// iff is either '0' or '1' public static bool IsBinary(this char c) => c == '0' || c == '1'; /// /// iff is not ,
/// all chars of are '.' or a digit, and
/// contains at most 1 decimal separator '.' ///
/// /// should exclude the suffix M.
/// This method returning for some does not imply that float.TryParse will also return .
/// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be IsUnsignedDecimal. ///
public static bool IsFixedPoint(this string? str) => !string.IsNullOrWhiteSpace(str) && str.HowMany('.') <= 1 && str.All(IsFixedPoint); /// iff is '.' or a digit /// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be IsUnsignedDecimal. public static bool IsFixedPoint(this char c) => IsUnsigned(c) || c == '.'; /// /// iff is not ,
/// the first char of is '-', '.', or a digit,
/// all subsequent chars of are '.' or a digit, and
/// contains at most 1 decimal separator '.' ///
/// /// should exclude the suffix f.
/// This method returning for some does not imply that float.TryParse will also return .
/// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be IsSignedDecimal. ///
public static bool IsFloat(this string? str) => !string.IsNullOrWhiteSpace(str) && str.HowMany('.') <= 1 && str[0].IsFloat() && str.Substring(1).All(IsFixedPoint); /// iff is '-', '.', or a digit /// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be IsSignedDecimal. public static bool IsFloat(this char c) => c.IsFixedPoint() || c == '-'; /// /// A copy of with characters removed so that the whole thing passes IsBinary.
/// That is, all chars of the copy will be either '0' or '1'. ///
public static string OnlyBinary(this string? raw) => string.IsNullOrWhiteSpace(raw) ? string.Empty : string.Concat(raw.Where(IsBinary)); /// /// A copy of with characters removed so that the whole thing passes IsUnsigned.
/// That is, all chars of the copy will be digits. ///
public static string OnlyUnsigned(this string? raw) => string.IsNullOrWhiteSpace(raw) ? string.Empty : string.Concat(raw.Where(IsUnsigned)); /// /// A copy of with characters removed so that the whole thing passes IsSigned.
/// That is, the first char of the copy will be '-' or a digit, and all subsequent chars of the copy will be digits. ///
/// If contains a serialized negative integer, it must be at the start ([0] == '-') or the sign will be dropped. public static string OnlySigned(this string? raw) { if (string.IsNullOrWhiteSpace(raw)) return string.Empty; return raw[0].IsSigned() ? raw[0] + string.Concat(raw.Skip(1).Where(IsUnsigned)) : string.Concat(raw.Skip(1).Where(IsUnsigned)); } /// /// A copy of with characters removed so that the whole thing passes IsHex.
/// That is, all chars of the copy will be hex digits ([0-9A-F]). ///
public static string OnlyHex(this string? raw) => string.IsNullOrWhiteSpace(raw) ? string.Empty : string.Concat(raw.Where(IsHex)).ToUpperInvariant(); /// /// A copy of with characters removed so that the whole thing passes IsFixedPoint.
/// That is, the all chars of the copy will be '.' or a digit and the copy will contain at most 1 decimal separator '.'. ///
/// /// The returned value may not be parseable by float.TryParse.
/// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be IsUnsignedDecimal. ///
public static string OnlyFixedPoint(this string? raw) { if (string.IsNullOrWhiteSpace(raw)) return string.Empty; var output = new StringBuilder(); var usedDot = false; foreach (var chr in raw) { if (chr == '.') { if (usedDot) continue; output.Append(chr); usedDot = true; } else if (chr.IsUnsigned()) output.Append(chr); } return output.ToString(); } /// /// A copy of with characters removed so that the whole thing passes IsFloat.
/// That is, the first char of the copy will be '-', '.', or a digit,
/// all subsequent chars of the copy will be '.' or a digit, and
/// the copy will contain at most 1 decimal separator '.'. ///
/// /// If contains a serialized negative decimal, it must be at the start ([0] == '-') or the sign will be dropped.
/// The returned value may not be parseable by float.TryParse.
/// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be IsSignedDecimal. ///
public static string OnlyFloat(this string? raw) { if (string.IsNullOrWhiteSpace(raw)) return string.Empty; var output = new StringBuilder(); var usedDot = false; var first = raw[0]; if (first.IsFloat()) { output.Append(first); if (first == '.') usedDot = true; } for (int i = 1, l = raw.Length; i < l; i++) { var chr = raw[i]; if (chr == '.') { if (usedDot) continue; output.Append(chr); usedDot = true; } else if (chr.IsUnsigned()) output.Append(chr); } return output.ToString(); } #pragma warning restore CS8602 } }